#

protected readonly copied = signal(false);

async copy() {
  try { await navigator.clipboard.writeText(text); } catch { return; }
  this.copied.set(true);
  setTimeout(() => this.copied.set(false), COPY_INTENT.autoReset);
}

<gf-icon-morph [intent]="COPY_INTENT" [active]="copied()" [animateAtRest]="true" [size]="18" />

Aa

#

protected readonly light = signal(false);

<button [attr.aria-label]="light() ? 'Dark theme' : 'Light theme'">
  <gf-icon-morph [intent]="THEME_INTENT" [active]="!light()" [animateAtRest]="true" [size]="20" />
</button>

#

@if (state() === 'sending') {
  <gf-icon [iconDef]="loaderCircleIcon" trigger="auto" [loop]="true" />
} @else {
  <gf-icon-morph [icon]="state() === 'sent' ? circleCheckIcon : sendIcon" />
}

#

<button [attr.aria-pressed]="liked()" (click)="toggle()">
  <gf-icon [iconDef]="heartIcon" [size]="18" trigger="tap" />
  {{ votes() }}
</button>

#

readonly playing = signal(false);

<button [attr.aria-pressed]="playing()" (click)="playing.set(!playing())">
  <gf-icon-morph
    [intent]="PLAY_PAUSE_INTENT"
    [active]="playing()"
    [animateAtRest]="true"
    [size]="20"
  />
</button>

#

<!-- No morph: the shape does not change, it ROTATES. -->
<gf-icon [iconDef]="chevronDownIcon" [size]="16" trigger="manual" [class.open]="open()" />

gf-icon { transition: transform 200ms cubic-bezier(0.16, 1, 0.3, 1); }
gf-icon.open { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) { gf-icon { transition: none; } }

#

<!-- Same shape, movement with intent: choreography. -->
<gf-icon [iconDef]="bellIcon" [size]="20" trigger="hover" />

#

readonly open = signal(false);
readonly icon = computed(() => (this.open() ? xIcon : searchIcon));

<input [attr.tabindex]="open() ? null : -1" />
<button [attr.aria-expanded]="open()" (click)="toggle()">
  <gf-icon-morph [icon]="icon()" [size]="18" spring="snappy" />
</button>

#

readonly favorite = signal(true);

<!-- "active" es el lado -Off del intent (starOff): se invierte para que "favorite" siga en positivo. -->
<button [attr.aria-pressed]="favorite()" (click)="favorite.set(!favorite())">
  <gf-icon-morph [intent]="FAVORITE_INTENT" [active]="!favorite()" [animateAtRest]="true" [size]="20" />
</button>

#

readonly muted = signal(false);

<button [attr.aria-pressed]="muted()" (click)="muted.set(!muted())">
  <gf-icon-morph [intent]="NOTIFY_INTENT" [active]="muted()" [animateAtRest]="true" [size]="20" />
</button>

#

readonly pinned = signal(true);

<!-- Mismo motivo que en favoritos: "active" es mapPinOff, se invierte. -->
<button [attr.aria-pressed]="pinned()" (click)="pinned.set(!pinned())">
  <gf-icon-morph [intent]="PIN_INTENT" [active]="!pinned()" [animateAtRest]="true" [size]="18" />
</button>

#

readonly muted = signal(false);

<button [attr.aria-pressed]="muted()" (click)="muted.set(!muted())">
  <gf-icon-morph [intent]="VOLUME_INTENT" [active]="muted()" [animateAtRest]="true" [size]="20" />
</button>

#

readonly removed = signal(false);

<button [attr.aria-pressed]="removed()" (click)="removed.set(!removed())">
  <gf-icon-morph [intent]="LIKE_INTENT" [active]="removed()" [animateAtRest]="true" [size]="18" />
</button>

#

readonly visible = signal(false);

<input [type]="visible() ? 'text' : 'password'" />
<button [attr.aria-pressed]="visible()" (click)="visible.set(!visible())">
  <gf-icon-morph
    [intent]="PASSWORD_INTENT"
    [active]="visible()"
    [animateAtRest]="true"
    [size]="18"
  />
</button>

#

readonly open = signal(false);

<button [attr.aria-expanded]="open()" (click)="open.set(!open())">
  Details
  <gf-icon-morph
    [intent]="EXPAND_COLLAPSE_INTENT"
    [active]="open()"
    [animateAtRest]="true"
    [size]="16"
  />
</button>