/* -- 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;
    }
  }
}
