/* =========================================
   1. The Core Relationship & Resets
   ========================================= */
/* Defines the Anchor */
[data-⚓️-name] {
  anchor-name: attr(data-⚓️-name type(<custom-ident>));
}

/* Defines the Target, enabling container queries */
/* box-sizing: border-box matters here specifically: anchor-size() returns
   the anchor's border-box dimension, and data-⚓️-sync-width/height assign it
   straight to `width`/`height`. Without border-box on the target, any of its
   own padding/border gets added on top, so the rendered size silently drifts
   from the anchor's — found via the test suite, not visible by eye until a
   target actually has padding. */
[data-⚓️-to] {
  position-anchor: attr(data-⚓️-to type(<custom-ident>));
  container-type: anchored;
  box-sizing: border-box;
}

/* Sub-Menu Context Handling */
[data-⚓️-to][data-⚓️-name] {
  transform-style: preserve-3d;
}

/* Native Top-Layer Reset */
/* [popover]/<dialog> ship UA styles (margin auto, inset: 0, fit-content
   sizing, max-size caps) that over-constrain anchor-positioned boxes. The
   whole selector is wrapped in :where() so it has ZERO specificity: it
   loses to every attribute rule below (margin: 0 used to sit at (0,2,0)
   and silently beat data-⚓️-align-self's margins on popovers) and to any
   author class, yet still beats the UA sheet, which is all it needs to do.
   inset/width/height are safe to reset now for the same reason — a
   zero-specificity `inset: auto` can no longer null out the anchor()
   longhands. Confirmed live: this alone makes data-⚓️-stretch-y size a
   popover correctly (it used to render ~20% short). */
:where(:is(dialog, [popover])[data-⚓️-to]) {
  margin: 0;
  inset: auto;
  width: auto;
  height: auto;
  max-width: none;
  max-height: none;
}

/* Automatically remove targets and layout modifiers from document flow */
[data-⚓️-to],
[data-⚓️-pin-all], [data-⚓️-pin-x], [data-⚓️-pin-y], [data-⚓️-pin-top], [data-⚓️-pin-bottom], [data-⚓️-pin-leading], [data-⚓️-pin-trailing],
[data-⚓️-above], [data-⚓️-below], [data-⚓️-before], [data-⚓️-after], [data-⚓️-side], [data-⚓️-side],
[data-⚓️-align-top], [data-⚓️-align-bottom], [data-⚓️-align-leading], [data-⚓️-align-trailing],
[data-⚓️-stretch-y], [data-⚓️-stretch-x], [data-⚓️-fill], [data-⚓️-area],
[data-⚓️-sync-width], [data-⚓️-sync-height],
[data-⚓️-align-x], [data-⚓️-align-y], [data-⚓️-center-all] {
  position: absolute;
}

[data-⚓️-root] { position: relative; }


/* =========================================
   2. Anchor Scoping (Component Isolation)
   ========================================= */
[data-⚓️-scope="all"] { anchor-scope: all; }
[data-⚓️-scope^="--"] { anchor-scope: attr(data-⚓️-scope type(<custom-ident>)); }
[data-⚓️-scope-name]  { anchor-scope: attr(data-⚓️-scope-name type(<custom-ident>)); }


/* =========================================
   3. Parent Constraints (Pin to Bounding Box)
   ========================================= */
[data-⚓️-pin-all]      { inset: 0; }
[data-⚓️-pin-y]        { inset-block: 0; }
[data-⚓️-pin-x]        { inset-inline: 0; }
[data-⚓️-pin-top]      { inset-block-start: 0; }
[data-⚓️-pin-bottom]   { inset-block-end: 0; }
[data-⚓️-pin-leading]  { inset-inline-start: 0; }
[data-⚓️-pin-trailing] { inset-inline-end: 0; }


/* =========================================
   4. Anchor Edge Bindings
   ========================================= */
/* Outside Edges & Inside Alignment. Everything here is a logical inset:
   the "end" attributes (above/before/align-bottom/align-trailing) use
   inset-block-end / inset-inline-end with anchor(), so they follow writing
   mode and direction (RTL, vertical-rl/lr) by construction, and native
   flip-block/flip-inline work on them unmodified. This replaced an earlier
   scheme that pinned the START edge and shifted the box back with a
   physical `translate` (--⚓️-tx/--⚓️-ty) to dodge an assumed Chromium
   "end-only inset" bug. That bug was really the UA stylesheet's `inset: 0`
   and `fit-content` sizing on [popover]/<dialog>, which the zero-
   specificity top-layer reset in section 1 now clears. Trade-off: switching
   between a start attribute and an end one (below <-> above) changes which
   inset is used, and inset can't interpolate from `auto`, so that swap
   snaps instead of tweening (same-attribute changes and size still morph). */
[data-⚓️-above^="--"]  { inset-block-end: anchor(attr(data-⚓️-above type(<custom-ident>)) self-start); }
[data-⚓️-above=""]     { inset-block-end: anchor(self-start); }

[data-⚓️-below^="--"]  { inset-block-start: anchor(attr(data-⚓️-below type(<custom-ident>)) self-end); }
[data-⚓️-below=""]     { inset-block-start: anchor(self-end); }

[data-⚓️-before^="--"] { inset-inline-end: anchor(attr(data-⚓️-before type(<custom-ident>)) self-start); }
[data-⚓️-before=""]    { inset-inline-end: anchor(self-start); }

[data-⚓️-after^="--"]  { inset-inline-start: anchor(attr(data-⚓️-after type(<custom-ident>)) self-end); }
[data-⚓️-after=""]     { inset-inline-start: anchor(self-end); }

/* Side — the same four outside edges as above/below/before/after, but as ONE
   attribute whose value is the side: data-⚓️-side="above|below|before|after".
   Same declarations as the booleans, so it behaves identically (logical,
   RTL-correct, native data-⚓️-try flips work). The point is state-driven UI:
   flipping a tooltip, docking a panel or mirroring a bubble is a single value
   change (el.setAttribute("data-⚓️-side", "above")) instead of removing one
   boolean and adding another, and two sides can never be set at once.
   Anchors through data-⚓️-to like everything else; there is no explicit
   named-anchor form (use the booleans for that). */
[data-⚓️-side="above"]  { inset-block-end: anchor(self-start); }
[data-⚓️-side="below"]  { inset-block-start: anchor(self-end); }
[data-⚓️-side="before"] { inset-inline-end: anchor(self-start); }
[data-⚓️-side="after"]  { inset-inline-start: anchor(self-end); }

/* Inside Alignment */
[data-⚓️-align-top^="--"]     { inset-block-start: anchor(attr(data-⚓️-align-top type(<custom-ident>)) self-start); }
[data-⚓️-align-top=""]        { inset-block-start: anchor(self-start); }

[data-⚓️-align-bottom^="--"]  { inset-block-end: anchor(attr(data-⚓️-align-bottom type(<custom-ident>)) self-end); }
[data-⚓️-align-bottom=""]     { inset-block-end: anchor(self-end); }

[data-⚓️-align-leading^="--"] { inset-inline-start: anchor(attr(data-⚓️-align-leading type(<custom-ident>)) self-start); }
[data-⚓️-align-leading=""]    { inset-inline-start: anchor(self-start); }

[data-⚓️-align-trailing^="--"]{ inset-inline-end: anchor(attr(data-⚓️-align-trailing type(<custom-ident>)) self-end); }
[data-⚓️-align-trailing=""]   { inset-inline-end: anchor(self-end); }

/* Anchor Stretching */
[data-⚓️-stretch-y^="--"] {
  inset-block-start: anchor(attr(data-⚓️-stretch-y type(<custom-ident>)) self-start);
  inset-block-end: anchor(attr(data-⚓️-stretch-y type(<custom-ident>)) self-end);
}
[data-⚓️-stretch-y=""] {
  inset-block-start: anchor(self-start);
  inset-block-end: anchor(self-end);
}

[data-⚓️-stretch-x^="--"] {
  inset-inline-start: anchor(attr(data-⚓️-stretch-x type(<custom-ident>)) self-start);
  inset-inline-end: anchor(attr(data-⚓️-stretch-x type(<custom-ident>)) self-end);
}
[data-⚓️-stretch-x=""] {
  inset-inline-start: anchor(self-start);
  inset-inline-end: anchor(self-end);
}

/* Anchor Fill — both axes at once, via the single `inset: anchor(inside)`
   form rather than data-⚓️-stretch-x + data-⚓️-stretch-y combined, PLUS an
   explicit `width`/`height` read from the same anchor with anchor-size().
   Both halves matter for anything that transitions:
   1. stretch-x + stretch-y (4 independent longhand insets) visibly
      collapses the box to ~0px wide mid-transition when its anchor moves
      (e.g. a CSS grid's tiles re-anchoring as the column count changes),
      even though the old and new size are identical. The single `inset`
      shorthand doesn't. (Chrome still reports four separate inset-*
      transitions for it, so why isn't established.)
   2. Even with the shorthand, a box sized purely by its insets collapses the
      same way; an explicit width/height makes the size authoritative so only
      the position animates. fill supplies it from the anchor itself, so
      there's nothing extra to remember — and if the anchor resizes the fill
      resizes with it. Author CSS that sets its own width/height (same
      specificity, later in the cascade) wins. */
[data-⚓️-fill^="--"] {
  inset: anchor(attr(data-⚓️-fill type(<custom-ident>)) inside);
  width: anchor-size(attr(data-⚓️-fill type(<custom-ident>)) inline);
  height: anchor-size(attr(data-⚓️-fill type(<custom-ident>)) block);
}
[data-⚓️-fill=""] {
  inset: anchor(inside);
  width: anchor-size(inline);
  height: anchor-size(block);
}

/* Tweenable end edges (opt-in: data-⚓️-tween). above/before/align-bottom/
   align-trailing normally use the END inset (inset-*-end) — logical and
   RTL/vertical-correct, but swapping between a start attribute and an end
   one changes WHICH inset is used and inset can't interpolate from `auto`,
   so below<->above snaps. data-⚓️-tween puts every edge attribute on the
   same START inset and gets the "shift back by my own size" from a
   percentage translate, which interpolates. The shift is a physical
   `translate`: correct in horizontal writing modes (the inline sign flips
   under :dir(rtl)), NOT in vertical ones, and native flips (data-⚓️-try)
   don't reset it, so don't combine the two. Pair with
   data-⚓️-transition="morph" or "glide". */
[data-⚓️-tween] {
  translate: var(--⚓️-tx, 0%) var(--⚓️-ty, 0%);
}
[data-⚓️-tween][data-⚓️-above=""]      { inset-block-end: auto; inset-block-start: anchor(self-start); --⚓️-ty: -100%; }
[data-⚓️-tween][data-⚓️-above^="--"]   { inset-block-end: auto; inset-block-start: anchor(attr(data-⚓️-above type(<custom-ident>)) self-start); --⚓️-ty: -100%; }
[data-⚓️-tween][data-⚓️-side="above"]       { inset-block-end: auto; inset-block-start: anchor(self-start); --⚓️-ty: -100%; }
[data-⚓️-tween][data-⚓️-side="before"]      { inset-inline-end: auto; inset-inline-start: anchor(self-start); --⚓️-tx: -100%; }
[data-⚓️-tween][data-⚓️-align-bottom=""]     { inset-block-end: auto; inset-block-start: anchor(self-end); --⚓️-ty: -100%; }
[data-⚓️-tween][data-⚓️-align-bottom^="--"]  { inset-block-end: auto; inset-block-start: anchor(attr(data-⚓️-align-bottom type(<custom-ident>)) self-end); --⚓️-ty: -100%; }
[data-⚓️-tween][data-⚓️-before=""]     { inset-inline-end: auto; inset-inline-start: anchor(self-start); --⚓️-tx: -100%; }
[data-⚓️-tween][data-⚓️-before^="--"]  { inset-inline-end: auto; inset-inline-start: anchor(attr(data-⚓️-before type(<custom-ident>)) self-start); --⚓️-tx: -100%; }
[data-⚓️-tween][data-⚓️-align-trailing=""]    { inset-inline-end: auto; inset-inline-start: anchor(self-end); --⚓️-tx: -100%; }
[data-⚓️-tween][data-⚓️-align-trailing^="--"] { inset-inline-end: auto; inset-inline-start: anchor(attr(data-⚓️-align-trailing type(<custom-ident>)) self-end); --⚓️-tx: -100%; }
[data-⚓️-tween][data-⚓️-before]:dir(rtl),
[data-⚓️-tween][data-⚓️-side="before"]:dir(rtl),
[data-⚓️-tween][data-⚓️-align-trailing]:dir(rtl) { --⚓️-tx: 100%; }
/* Stretch and center join in the same way: stretch keeps its start inset and
   takes its size from anchor-size() instead of the end inset (size
   interpolates from auto via interpolate-size, an inset can't), and center
   moves off anchor-center onto anchor(center) minus half its own size. So
   every edge, stretch and center state shares the one start inset per axis
   and any swap between them tweens. :where() keeps the size at (0,1,0) so
   data-⚓️-sync-width/height (section 5, later) still win. */
[data-⚓️-tween]:where([data-⚓️-stretch-y=""])     { inset-block-end: auto; height: anchor-size(block); }
[data-⚓️-tween]:where([data-⚓️-stretch-y^="--"])  { inset-block-end: auto; height: anchor-size(attr(data-⚓️-stretch-y type(<custom-ident>)) block); }
[data-⚓️-tween]:where([data-⚓️-stretch-x=""])     { inset-inline-end: auto; width: anchor-size(inline); }
[data-⚓️-tween]:where([data-⚓️-stretch-x^="--"])  { inset-inline-end: auto; width: anchor-size(attr(data-⚓️-stretch-x type(<custom-ident>)) inline); }
[data-⚓️-tween]:is([data-⚓️-align-y="center"], [data-⚓️-center-all]) { align-self: normal; inset-block-start: anchor(center); --⚓️-ty: -50%; }
[data-⚓️-tween]:is([data-⚓️-align-x="center"], [data-⚓️-center-all]) { justify-self: normal; inset-inline-start: anchor(center); --⚓️-tx: -50%; }
[data-⚓️-tween]:is([data-⚓️-align-x="center"], [data-⚓️-center-all]):dir(rtl) { --⚓️-tx: 50%; }


/* =========================================
   5. Sizing Synchronization (with Auto Layout Math)
   ========================================= */
/* Explicit Routing */
[data-⚓️-sync-width^="--"] {
  width: calc((anchor-size(attr(data-⚓️-sync-width type(<custom-ident>)) inline) * var(--⚓️-mult-x, 1)) + var(--⚓️-offset-x, 0px));
}
[data-⚓️-sync-height^="--"] {
  height: calc((anchor-size(attr(data-⚓️-sync-height type(<custom-ident>)) block) * var(--⚓️-mult-y, 1)) + var(--⚓️-offset-y, 0px));
}

/* Implicit Boolean (Defaults to Target) */
[data-⚓️-sync-width=""] {
  width: calc((anchor-size(inline) * var(--⚓️-mult-x, 1)) + var(--⚓️-offset-x, 0px));
}
[data-⚓️-sync-height=""] {
  height: calc((anchor-size(block) * var(--⚓️-mult-y, 1)) + var(--⚓️-offset-y, 0px));
}


/* =========================================
   6. Position Area & Alignment
   ========================================= */
[data-⚓️-area="top"]      { position-area: block-start; }
[data-⚓️-area="bottom"]   { position-area: block-end; }
[data-⚓️-area="leading"]  { position-area: inline-start; }
[data-⚓️-area="trailing"] { position-area: inline-end; }

[data-⚓️-align-x="center"] { justify-self: anchor-center; }
[data-⚓️-align-y="center"] { align-self: anchor-center; }
[data-⚓️-center-all]       { place-self: anchor-center; }

[data-⚓️-align-self="center"] { margin: auto; }
[data-⚓️-align-self="start"]  { margin-inline-end: auto; }
[data-⚓️-align-self="end"]    { margin-inline-start: auto; }


/* =========================================
   7. Collision Resolution (@position-try)
   ========================================= */
/* Native flip keywords. With the top-layer reset in section 1 and the
   logical insets in section 4 they work on popovers, plain elements and
   every attribute, above/before included (named or implicit form), in both
   directions — no custom @position-try blocks needed. */
[data-⚓️-try="flip-y"]   { position-try-fallbacks: flip-block; }
[data-⚓️-try="flip-x"]   { position-try-fallbacks: flip-inline; }
[data-⚓️-try="flip-all"] { position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline; }

[data-⚓️-try-order="most-y"] { position-try-order: most-block-size; }
[data-⚓️-try-order="most-x"] { position-try-order: most-inline-size; }


/* =========================================
   8. Visibility State
   ========================================= */
/* The CSSWG renamed the plural keywords to singular (anchors-visible ->
   anchor-visible, anchors-valid -> anchor-valid) and Chrome 157 ships the
   singular names, keeping the plurals as deprecated aliases; anchor-valid
   itself is brand new there (the old plural anchors-valid was never
   shippable). Each value therefore declares the plural first and the
   singular second: a browser that doesn't know a keyword drops that whole
   declaration at parse time, so older engines keep the plural (where it
   exists) and newer ones take the singular, with no @supports needed. */
[data-⚓️-visibility="always"]              { position-visibility: always; }
[data-⚓️-visibility="valid"]               { position-visibility: anchors-valid; position-visibility: anchor-valid; }
[data-⚓️-visibility="visible"]             { position-visibility: anchors-visible; position-visibility: anchor-visible; }
[data-⚓️-visibility="no-overflow"]         { position-visibility: no-overflow; }
[data-⚓️-visibility="valid-no-overflow"]   { position-visibility: anchors-valid no-overflow; position-visibility: anchor-valid no-overflow; }
[data-⚓️-visibility="visible-no-overflow"] { position-visibility: anchors-visible no-overflow; position-visibility: anchor-visible no-overflow; }
[data-⚓️-visibility="all"]                 { position-visibility: anchors-valid anchors-visible no-overflow; position-visibility: anchor-valid anchor-visible no-overflow; }


/* =========================================
   9. AIM (Anchor Interpolated Morphing)
   ========================================= */
/* Enable keyword interpolation (like height: auto) globally for targets */
[data-⚓️-root], [data-⚓️-to] {
  interpolate-size: allow-keywords;
}

/* The Morph Transition Utility */
[data-⚓️-transition="morph"] {
  /* Use a nice spatial bezier for the geometry, and ease for opacity/colors */
  transition:
    inset 0.5s cubic-bezier(0.25, 1, 0.5, 1),
    width 0.5s cubic-bezier(0.25, 1, 0.5, 1),
    height 0.5s cubic-bezier(0.25, 1, 0.5, 1),
    position-area 0.5s cubic-bezier(0.25, 1, 0.5, 1),
    translate 0.5s cubic-bezier(0.25, 1, 0.5, 1),
    opacity 0.4s ease;
}

/* The Glide Transition Utility: geometry only, short, no entry animation and
   no opacity — for layouts that reflow while visible (a grid re-flowing on
   resize, a list re-sorting) rather than things that appear. Same
   properties as morph so tween/fill/anchor resizes all animate. */
[data-⚓️-transition="glide"] {
  transition:
    inset 0.25s cubic-bezier(0.25, 1, 0.5, 1),
    width 0.25s cubic-bezier(0.25, 1, 0.5, 1),
    height 0.25s cubic-bezier(0.25, 1, 0.5, 1),
    translate 0.25s cubic-bezier(0.25, 1, 0.5, 1);
}

/*
  ENTRY MORPH: @starting-style
  Tells the browser to start the element's lifecycle perfectly collapsed
  inside the bounding box of the anchor before it transitions to its final layout.
*/
@starting-style {

  /* Explicit Routing */
  :is(dialog, [popover])[data-⚓️-morph-from^="--"],
  [data-⚓️-morph-from^="--"]:not([hidden]) {
    inset-block-start: anchor(attr(data-⚓️-morph-from type(<custom-ident>)) self-start);
    inset-block-end: anchor(attr(data-⚓️-morph-from type(<custom-ident>)) self-end);
    inset-inline-start: anchor(attr(data-⚓️-morph-from type(<custom-ident>)) self-start);
    inset-inline-end: anchor(attr(data-⚓️-morph-from type(<custom-ident>)) self-end);

    width: anchor-size(attr(data-⚓️-morph-from type(<custom-ident>)) inline);
    height: anchor-size(attr(data-⚓️-morph-from type(<custom-ident>)) block);

    opacity: 0;
  }

  /* Implicit Boolean (Defaults to the element's target) */
  :is(dialog, [popover])[data-⚓️-morph-from=""],
  [data-⚓️-morph-from=""]:not([hidden]) {
    inset-block-start: anchor(self-start);
    inset-block-end: anchor(self-end);
    inset-inline-start: anchor(self-start);
    inset-inline-end: anchor(self-end);

    width: anchor-size(inline);
    height: anchor-size(block);

    opacity: 0;
  }
}


/* =========================================
   10. Reduced Motion
   ========================================= */
/* Keep the opacity fade (no spatial travel) and snap geometry: morph
   otherwise animates inset/size/position-area/translate for 0.5s and runs
   the @starting-style entry from a collapsed box. */
@media (prefers-reduced-motion: reduce) {
  [data-⚓️-transition="morph"] {
    transition: opacity 0.2s ease;
  }

  [data-⚓️-transition="glide"] {
    transition: none;
  }
}
