/* ============================================================================
   THE CABDESIGN PANEL CONTRACT — one slide-in language for every product
   ----------------------------------------------------------------------------
   Source of truth: docs/research/p487-menu-system-proposal-2026-08-26.md §6.
   Adopted by the founder as FD287 Option A, 2026-08-27, verbatim:

     "Go ahead and implement option A, and we'll see how I like it. If I don't
      like it, then I'll have you change it."

   FOUR KINDS OF SURFACE AND NO FIFTH. Every panel-like surface in CabDesign and
   StackDesign declares which kind it is with `data-bws-panel`; a surface that
   fits none of them is a design question, not a new kind.

     menu     — a short list of destinations or commands, anchored to the rail
                button that opened it. Overlays, never a scrim, transient.
     panel    — the slide-in. A working surface you keep open. RESERVES its own
                column; never a scrim; never covers the drawing.
     popover  — one transient reference or pick, anchored to its own control.
     dialog   — committing a set of decisions, or a destructive confirm. The one
                kind that carries a scrim and blocks the app.
     teaching — declared OUT of the contract on purpose (coach marks, the guided
                tour). Writing them down as out-of-scope is what stops a future
                session "standardizing" them (§7 row 13).

   THE ONE-LINE TEST (§6.0): does the person need to keep looking at their work
   while this is open? Yes -> panel or popover, no scrim. No, because they are
   committing or destroying something -> dialog.

   THE WIDTHS ARE NOT INVENTED (§6.3). Every number below is a width the product
   already shipped, promoted to a token so a reader learns three widths instead
   of eight: 302 is the rail flyout, 340 is the inspector/shortcuts dock column,
   390 is the AI dock, 420 is the cut-list peek's cap.

   THE MOTION IS NOT INVENTED EITHER (§6.5, §4.2). 220ms is what the AI dock and
   both library drawers already ship, and it sits inside the 200-300ms band NN/g
   gives a substantial screen change; the shorter 160ms exit and the two curves
   are NN/g's ("animating objects appearing... need a subtly longer duration than
   objects disappearing", ease-out entering, ease-in exiting).
   https://www.nngroup.com/articles/animation-duration/

   WHERE THIS LIVES AND WHY IT IS SHARED. §6.8 puts the contract in one file that
   every page loads, "so the grammar cannot fork per page" — the same shape that
   already works for shared/bws-modal.js. Wave 1 (this file's first consumer) is
   cabinet-designer.html; materials.html, hardware.html and StackDesign's
   assistant adopt it in waves 2 and 3 without a second copy of these numbers.
   ============================================================================ */

:root {
  /* --- The width scale (§6.3) ------------------------------------------- */
  --bws-panel-sm: 302px;   /* menu flyouts, and the cabinet-editor parts column */
  --bws-panel-md: 340px;   /* inspector, shortcuts, any reference panel        */
  --bws-panel-lg: 390px;   /* the AI dock, browse and edit panels              */
  --bws-pop-max: 420px;    /* the cap on any popover                           */

  /* --- The one motion speed (§6.5) -------------------------------------- */
  --bws-panel-in: 220ms;                       /* what the AI dock already ships */
  --bws-panel-out: 160ms;                      /* exits are shorter — NN/g       */
  --bws-ease-out: cubic-bezier(0.2, 0, 0, 1);  /* entering: fast, then settles   */
  --bws-ease-in: cubic-bezier(0.4, 0, 1, 1);   /* exiting: accelerates away      */

  /* --- The dialog cap (§6.6) -------------------------------------------- */
  /* A blocking surface still keeps the work framed: CabBuilder's measured
     67.5% x 83% proportion, widened for our narrower minimum viewport. */
  --bws-dialog-w: min(1180px, 78vw);
  --bws-dialog-h: min(880px, 84vh);
}

/* Reduced motion REPLACES the motion rather than merely shortening it (MDN's
   recommended pattern): the state change stays instant and COMPLETE — the panel
   still opens, it just does not travel. */
@media (prefers-reduced-motion: reduce) {
  :root {
    --bws-panel-in: 0.01ms;
    --bws-panel-out: 0.01ms;
  }
}

/* ---------------------------------------------------------------------------
   THE ENTER MOTION, DECLARED PER SURFACE

   Only `transform` and `opacity` are animated (§6.5 rule 1). Never width, left,
   display or height — they force layout every frame, and "works really fast" is
   a frame-time claim before it is an easing claim.

   Kinds 1 and 3 enter as a 6-8px travel plus a fade, from the edge nearest their
   anchor (rule 2): a full-width slide on a 302px card reads as slow. Kind 2
   enters as a full translateX(100%) -> 0 from the edge it docks against.

   WHICH KIND-2 SURFACES TRAVEL, AND WHICH ONLY FADE. Rule 3's full slide belongs
   to a panel whose RESERVE travels with it: a fixed overlay panel sliding in while
   the canvas pads itself by the same width on the same curve. That is the AI dock,
   and it is the wave-2 shape for the Materials and Hardware drawers, so
   edge-left/edge-right stay defined here for them.

   A GRID-docked panel is a different animal, and pretending otherwise was measured
   twice. Its reserve is a grid track, and a track cannot travel - it simply exists.
   Start such a panel at translateX(100%) and for 220ms it is outside the viewport;
   start it 8px off and for 220ms its box is 8px from where it says it is. Both broke
   shipped assertions that read the panel's rectangle the instant it opens (the
   docked inspector inside the viewport, the narrow sheet flush to both edges), and
   both were the panel telling the truth about where it was. A rectangle is not a
   decoration to animate: it is what a pointer, a hit test and a screen reader all
   read. So the inspector, the shortcuts panel, the Catalog panel and the cabinet
   editor's parts column FADE into a column that is already theirs, and travel is
   left to the surfaces that overlay rather than occupy.

   THE PICK, RECORDED WITH ITS CONSTRAINT (the proposal leaves the mechanism
   open; this is the choice, made once):
     A surface whose closed state is `display: none` — which is every surface in
     the Designer that toggles the `hidden` attribute — ENTERS with motion and
     LEAVES instantly. It cannot spend `--bws-panel-out`, because the attribute
     cannot be deferred behind an exit:
       - tests/e2e/journeys/designer-hidden-integrity.test.mjs requires EVERY
         element carrying the hidden attribute to compute display:none, so the
         closed state may not be faked with visibility or opacity; and
       - tests/e2e/journeys/designer-front-styles.test.mjs (d) reads
         `.hidden === true` 100ms after Escape, so the attribute may not be set
         160ms late either.
     A surface that never leaves the box model — the AI dock, which slides on a
     transform — honours BOTH tokens. That is why --bws-panel-out is consumed
     rather than decorative.

   A one-shot `animation` (not a transition) is what runs the enter: display
   flipping away from `none` starts it, so there is no JS state, no timer, and no
   second thing to keep in step with the `hidden` attribute.
   --------------------------------------------------------------------------- */

@keyframes bws-enter-from-left  { from { opacity: 0; transform: translateX(-8px); } to { opacity: 1; transform: none; } }
@keyframes bws-enter-from-right { from { opacity: 0; transform: translateX(8px);  } to { opacity: 1; transform: none; } }
@keyframes bws-enter-from-above { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: none; } }
@keyframes bws-enter-from-below { from { opacity: 0; transform: translateY(8px);  } to { opacity: 1; transform: none; } }
@keyframes bws-enter-edge-right { from { transform: translateX(100%);  } to { transform: none; } }
@keyframes bws-enter-edge-left  { from { transform: translateX(-100%); } to { transform: none; } }
@keyframes bws-enter-dialog     { from { opacity: 0; transform: scale(0.98); } to { opacity: 1; transform: none; } }
@keyframes bws-enter-fade       { from { opacity: 0; } to { opacity: 1; } }

/* NOTHING ANIMATES ON FIRST PAINT (§6.5 rule 5). A panel restored open at boot is
   simply open, so the whole arm is gated on a flag the page sets one frame after
   it is laid out. */
:root[data-bws-motion="on"] [data-bws-enter="left"]       { animation: bws-enter-from-left  var(--bws-panel-in) var(--bws-ease-out) both; }
:root[data-bws-motion="on"] [data-bws-enter="right"]      { animation: bws-enter-from-right var(--bws-panel-in) var(--bws-ease-out) both; }
:root[data-bws-motion="on"] [data-bws-enter="above"]      { animation: bws-enter-from-above var(--bws-panel-in) var(--bws-ease-out) both; }
:root[data-bws-motion="on"] [data-bws-enter="below"]      { animation: bws-enter-from-below var(--bws-panel-in) var(--bws-ease-out) both; }
:root[data-bws-motion="on"] [data-bws-enter="edge-right"] { animation: bws-enter-edge-right var(--bws-panel-in) var(--bws-ease-out) both; }
:root[data-bws-motion="on"] [data-bws-enter="edge-left"]  { animation: bws-enter-edge-left  var(--bws-panel-in) var(--bws-ease-out) both; }
:root[data-bws-motion="on"] [data-bws-enter="dialog"]     { animation: bws-enter-dialog     var(--bws-panel-in) var(--bws-ease-out) both; }
:root[data-bws-motion="on"] [data-bws-enter="fade"]       { animation: bws-enter-fade       var(--bws-panel-in) var(--bws-ease-out) both; }

@media (prefers-reduced-motion: reduce) {
  :root[data-bws-motion="on"] [data-bws-enter] { animation: none; }
}

/* ---------------------------------------------------------------------------
   ELEVATION FOLLOWS THE KIND, NOT THE TASTE (§6.2)

   A docked kind-2 panel is flush: no radius, one border on the edge it meets the
   canvas at, NO shadow. Kinds 1 and 3 overlay, so they carry the radius, the one
   border and one shadow. These are written as the contract's floor; a page may
   place a surface, but it may not give a docked panel a drop shadow.

   Chrome is never brown (FD166). Content INSIDE a panel — swatches, the
   viewport, wood colours — is exempt permanently.

   WRITTEN, NOT ENFORCED HERE, and that is deliberate rather than lazy. "Docked"
   is not a property of the element: the same panel is a docked column above
   1200px and a fixed bottom sheet below it, and which one it is lives in the
   page's own breakpoint (`:root[data-catalog-panel="open"]` and its siblings).
   A shared selector claiming to know cannot know, and a rule that flattened a
   bottom sheet's radius at every width would be a cosmetic defect shipped in the
   name of consistency. The page states it per surface; this states the law.
   --------------------------------------------------------------------------- */

/* One occupant per edge, at every width (§6.3). Two occupants in one grid cell is
   how the cut-list peek became an invisible dead button (BUG-2026-08-05-02); the
   page enforces the pairing, this is the reminder that it is a law and not a
   repeated patch. */
