/* -- FAB (floating action button) + speed dial ---------------------- */

@layer components {
  /* A .fab holds a .fab-trigger (a .btn with popovertarget) and, for a speed
     dial, a .fab-menu[popover] of .fab-action buttons. The popover is the
     whole behavior: click / Enter toggles it, Escape and an outside click
     close it, the trigger reports aria-expanded - no JavaScript. The menu is
     anchored to the trigger (anchor-scope keeps many FABs apart) and stacks
     its actions away from it: data-direction up (default) / down / left /
     right, or data-layout="flower" for a quarter circle. */
  .fab {
    --_trig: 3.5rem;
    --_act: 2.75rem;
    --_gap: 0.75rem;
    --_edge: 1.5rem;
    anchor-scope: --fab;
    position: fixed;
    z-index: 50;
    inset-block-end: var(--_edge);
    inset-inline-end: var(--_edge);
    display: flex;

    /* -- Position: the corner (or edge center) it floats in ------------- */
    &[data-position="bottom-start"] { inset-inline-end: auto; inset-inline-start: var(--_edge); }
    &[data-position="top-end"] { inset-block-end: auto; inset-block-start: var(--_edge); }
    &[data-position="top-start"] { inset: var(--_edge) auto auto var(--_edge); }
    &[data-position="bottom-center"] { inset-inline: 0; justify-content: center; pointer-events: none; & > * { pointer-events: auto; } }
    /* inside a positioned container (a card, a panel) instead of the page */
    &[data-attach="container"] { position: absolute; }

    /* -- Sizes: trigger + actions scale together (md == default) --------- */
    &[data-size="sm"] { --_trig: 2.75rem; --_act: 2.25rem; --_gap: 0.5rem; }
    &[data-size="md"] { --_trig: 3.5rem; --_act: 2.75rem; --_gap: 0.75rem; }
    &[data-size="lg"] { --_trig: 4.25rem; --_act: 3.25rem; --_gap: 0.875rem; }
  }

  /* -- Trigger ------------------------------------------------------------ */
  .fab-trigger {
    anchor-name: --fab;
    width: var(--_trig);
    height: var(--_trig);
    padding: 0;
    border-radius: 9999px;
    box-shadow: var(--shadow-lg);

    & svg { width: 1.375rem; height: 1.375rem; }
    & .fab-icon { display: inline-grid; place-items: center; font-size: 1.375rem; line-height: 1; transition: rotate 200ms ease; }

    /* extended: icon + label, a pill */
    /* icon-only width == the round trigger, so a folded label leaves a circle */
    &:has(.fab-text) { width: auto; min-width: var(--_trig); padding-inline: calc((var(--_trig) - 1.375rem) / 2); gap: 0; }
    & .fab-text { margin-inline-start: 0.625rem; padding-inline-end: 0.25rem; }
  }
  .fab-text {
    overflow: hidden;
    white-space: nowrap;
    font-size: 0.9375rem;
  }
  /* open: the trigger's icon turns (+ becomes ×) - or swaps with
     .fab-icon-open / .fab-icon-closed */
  .fab:has(.fab-menu:popover-open) .fab-trigger .fab-icon:not(.fab-icon-open, .fab-icon-closed) { rotate: 45deg; }
  .fab:not(:has(.fab-menu:popover-open)) .fab-icon-open,
  .fab:has(.fab-menu:popover-open) .fab-icon-closed { display: none; }

  /* -- The menu (a popover in the top layer, anchored to the trigger) ----- */
  .fab-menu[popover] {
    position: fixed;
    position-anchor: --fab;
    inset: auto;
    width: auto;
    height: auto;
    margin: 0;
    padding: 0;
    border: 0;
    background: none;
    color: inherit;
    overflow: visible;
    display: none;
    gap: var(--_gap);
    align-items: center;

    /* up (default): stacked above the trigger, first action nearest */
    bottom: anchor(bottom);
    left: anchor(center);
    translate: -50% 0;
    flex-direction: column-reverse;
    padding-block-end: calc(var(--_trig) + var(--_gap));

    &:popover-open { display: flex; }

    /* the menu box overlaps the trigger (its padding leaves room for it) - only
       the actions take the pointer, so the trigger stays clickable to close */
    pointer-events: none;
    & > * { pointer-events: auto; }

    transition: display 180ms allow-discrete, overlay 180ms allow-discrete;
  }
  .fab[data-direction="down"] .fab-menu[popover] {
    bottom: auto;
    top: anchor(top);
    flex-direction: column;
    padding-block: calc(var(--_trig) + var(--_gap)) 0;
  }
  .fab[data-direction="left"] .fab-menu[popover] {
    bottom: auto;
    left: auto;
    right: anchor(right);
    top: anchor(center);
    translate: 0 -50%;
    flex-direction: row-reverse;
    padding: 0 calc(var(--_trig) + var(--_gap)) 0 0;
  }
  .fab[data-direction="right"] .fab-menu[popover] {
    bottom: auto;
    left: anchor(left);
    top: anchor(center);
    translate: 0 -50%;
    flex-direction: row;
    padding: 0 0 0 calc(var(--_trig) + var(--_gap));
  }
  /* an optional dim layer behind the open dial */
  .fab[data-backdrop] .fab-menu::backdrop {
    background-color: color-mix(in oklch, var(--foreground) 22%, transparent);
    backdrop-filter: blur(2px);
  }

  /* text dials: the pills line up with the trigger's outer edge */
  .fab-menu[popover]:has(> .fab-action > .fab-text) {
    left: auto;
    right: anchor(right);
    translate: none;
    align-items: flex-end;
  }
  .fab:is([data-position="bottom-start"], [data-position="top-start"]) .fab-menu[popover]:has(> .fab-action > .fab-text) {
    right: auto;
    left: anchor(left);
    align-items: flex-start;
  }

  /* -- Actions ------------------------------------------------------------ */
  .fab-action {
    position: relative;
    display: inline-grid;
    place-items: center;
    width: var(--_act);
    height: var(--_act);
    padding: 0;
    border: 1px solid var(--border);
    border-radius: 9999px;
    background-color: var(--popover);
    color: var(--popover-foreground);
    box-shadow: var(--shadow-md);
    font: inherit;
    font-size: 1.125rem;
    line-height: 1;
    text-decoration: none;
    cursor: pointer;
    transition: opacity 160ms ease, scale 160ms ease, translate 160ms ease, background-color 150ms ease;
    transition-delay: calc(var(--_i, 0) * 35ms);

    &:hover { background-color: var(--accent); color: var(--accent-foreground); }
    &:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
    & svg { width: 1.125rem; height: 1.125rem; }
    /* .btn tones / variants work on an action too */
    &.btn { border-radius: 9999px; width: var(--_act); height: var(--_act); padding: 0; }
  }
  /* text actions: a pill with the words inside */
  .fab-action:has(> .fab-text) { display: inline-flex; align-items: center; width: auto; padding-inline: 0.875rem 1.125rem; gap: 0.5rem; white-space: nowrap; font-size: 0.875rem; font-weight: 500; }
  .fab-action .fab-text { font-size: inherit; }

  /* the stagger: actions pop out from the trigger one after another */
  .fab-menu > :nth-child(2) { --_i: 1; }
  .fab-menu > :nth-child(3) { --_i: 2; }
  .fab-menu > :nth-child(4) { --_i: 3; }
  .fab-menu > :nth-child(5) { --_i: 4; }
  .fab-menu > :nth-child(6) { --_i: 5; }
  .fab-menu > :nth-child(7) { --_i: 6; }
  .fab-menu:not(:popover-open) > * { opacity: 0; scale: 0.6; }
  @starting-style {
    .fab-menu:popover-open > * { opacity: 0; scale: 0.6; }
    .fab[data-layout="flower"] .fab-menu:popover-open > * { translate: -50% -50%; }
  }

  /* label chips beside the actions (toward the screen's middle) */
  .fab-label {
    position: absolute;
    inset-inline-end: calc(100% + 0.625rem);
    top: 50%;
    translate: 0 -50%;
    padding: 0.25rem 0.625rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background-color: var(--popover);
    color: var(--popover-foreground);
    box-shadow: var(--shadow-sm);
    font-size: 0.8125rem;
    font-weight: 500;
    white-space: nowrap;
    pointer-events: none;
  }
  .fab:is([data-position="bottom-start"], [data-position="top-start"]) .fab-label {
    inset-inline-end: auto;
    inset-inline-start: calc(100% + 0.625rem);
  }
  /* sideways dials: the label sits above its action */
  .fab:is([data-direction="left"], [data-direction="right"]) .fab-label {
    inset-inline: auto;
    left: 50%;
    top: auto;
    bottom: calc(100% + 0.5rem);
    translate: -50% 0;
  }

  /* -- Main action: an action that takes the trigger's place when open ---- */
  .fab-menu > .fab-main {
    --_i: 0;
    width: var(--_trig);
    height: var(--_trig);
    border: 0;
    background-color: var(--primary);
    color: var(--primary-foreground);
    box-shadow: var(--shadow-lg);
    font-size: 1.375rem;

    &:hover { background-color: color-mix(in oklch, var(--primary) 88%, var(--primary-foreground)); color: var(--primary-foreground); }
    & svg { width: 1.375rem; height: 1.375rem; }
  }
  .fab-menu:has(> .fab-main) { padding: 0 !important; }
  .fab-menu:has(> .fab-main) > :not(.fab-main) { --_i: 1; }

  /* -- Flower: the actions on a quarter circle around the trigger --------
     The arc opens toward the screen's middle (up-left from bottom-end);
     2 - 6 actions spread evenly. Labels are visually hidden here (they
     still name the buttons). */
  .fab[data-layout="flower"] {
    --_r: calc(var(--_trig) + var(--_act) * 1.6);
    --_arc: 90deg;
    --_a0: 0deg;
    --_sx: -1;
    --_sy: -1;

    &:is([data-position="bottom-start"], [data-position="top-start"]) { --_sx: 1; }
    &:is([data-position="top-end"], [data-position="top-start"]) { --_sy: 1; }
    /* from the center of an edge (a dock's raised action): a semicircle */
    /* the upper arc only (30° - 150°): the ends stay clear of whatever sits
       level with the trigger (a dock's neighbouring items) */
    &:is([data-position="bottom-center"], [data-position="top-center"]) {
      --_arc: 120deg;
      --_a0: 30deg;
      --_sx: -1;
      --_r: calc(var(--_trig) + var(--_act) * 0.9);
    }
    &[data-position="bottom-center"] { --_sy: -1; }
    &[data-position="top-center"] { --_sy: 1; }

    & .fab-menu[popover] {
      top: anchor(center);
      left: anchor(center);
      bottom: auto;
      right: auto;
      width: 0;
      height: 0;
      padding: 0;
      translate: none;
      display: none;
      &:popover-open { display: block; }
    }
    & .fab-menu > * {
      --_step: var(--_arc);
      --_a: calc(var(--_a0) + var(--_k, 0) * var(--_step));
      position: absolute;
      left: 0;
      top: 0;
      translate: calc(-50% + var(--_sx) * cos(var(--_a)) * var(--_r)) calc(-50% + var(--_sy) * sin(var(--_a)) * var(--_r));
    }
    & .fab-menu:not(:popover-open) > * { translate: -50% -50%; }
    & .fab-label { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip-path: inset(50%); border: 0; }

    /* k = 0 … n-1 along the arc (the main action, if any, stays centered) */
    & .fab-menu > :not(.fab-main):nth-child(1 of :not(.fab-main)) { --_k: 0; }
    & .fab-menu > :not(.fab-main):nth-child(2 of :not(.fab-main)) { --_k: 1; }
    & .fab-menu > :not(.fab-main):nth-child(3 of :not(.fab-main)) { --_k: 2; }
    & .fab-menu > :not(.fab-main):nth-child(4 of :not(.fab-main)) { --_k: 3; }
    & .fab-menu > :not(.fab-main):nth-child(5 of :not(.fab-main)) { --_k: 4; }
    & .fab-menu > :not(.fab-main):nth-child(6 of :not(.fab-main)) { --_k: 5; }
    & .fab-menu:has(> :not(.fab-main):nth-child(3 of :not(.fab-main))) > * { --_step: calc(var(--_arc) / 2); }
    & .fab-menu:has(> :not(.fab-main):nth-child(4 of :not(.fab-main))) > * { --_step: calc(var(--_arc) / 3); }
    & .fab-menu:has(> :not(.fab-main):nth-child(5 of :not(.fab-main))) > * { --_step: calc(var(--_arc) / 4); --_r: calc(var(--_trig) + var(--_act) * 2.1); }
    & .fab-menu:has(> :not(.fab-main):nth-child(6 of :not(.fab-main))) > * { --_step: calc(var(--_arc) / 5); }
    & .fab-menu > .fab-main { translate: -50% -50%; }
  }

  /* -- Extended FAB that shrinks to its icon as the page scrolls ----------
     data-shrink="scroll": a scroll-driven animation (no listener) folds the
     label away over the first 120px of scrolling. It follows the page;
     for a scrolling container, name its timeline (scroll-timeline: --list;
     timeline-scope on a shared ancestor) and set --fab-timeline: --list. */
  @keyframes fab-shrink {
    to { max-width: 0; opacity: 0; margin-inline-start: 0; padding-inline-end: 0; }
  }
  .fab[data-shrink="scroll"] .fab-trigger {
    & .fab-text {
      max-width: 12rem;
      animation: fab-shrink linear both;
      animation-timeline: var(--fab-timeline, scroll(root block));
      animation-range: 0 120px;
    }
  }

  /* -- Back to top --------------------------------------------------------
     data-scroll-top: the trigger is a link to the start (href="#top" - the
     browser scrolls the document to its top natively, even without an
     element of that id; for a scrolling container, link to an element at
     its start). A scroll-driven animation keeps it hidden at the top and
     fades / grows it in between --fab-scroll-top-from and -to of scroll
     (default 160px - 320px); visibility follows, so it is not focusable
     while hidden. The timeline is a var() - minifiers leave it alone. */
  @keyframes fab-appear {
    from { opacity: 0; scale: 0.6; visibility: hidden; }
  }
  .fab[data-scroll-top] .fab-trigger {
    animation: fab-appear linear both;
    animation-timeline: var(--fab-timeline, scroll(root block));
    animation-range: var(--fab-scroll-top-from, 160px) var(--fab-scroll-top-to, 320px);
  }
  /* the jump glides - for people who have not asked for less motion */
  @media (prefers-reduced-motion: no-preference) {
    html:has(.fab[data-scroll-top]) { scroll-behavior: smooth; }
  }

  /* -- Accessibility -------------------------------------------------------- */
  @media (prefers-reduced-motion: reduce) {
    .fab-action, .fab-trigger .fab-icon, .fab-menu[popover] { transition: none; }
    .fab[data-shrink="scroll"] .fab-trigger .fab-text { animation: none; }
    .fab[data-scroll-top] .fab-trigger { animation: none; }
  }
  @media (forced-colors: active) {
    .fab-action, .fab-label { border: 1px solid CanvasText; }
  }
}
