# Invite-Teammates

> A refined invite flow with animated teammate cards — collaboration UI that feels light and intentional.

**Definition:** Invite-Teammates is a cards UI animation from collections.html. It is built with HTML, CSS, and JavaScript, ships with a live preview, and is free to copy into personal and commercial projects.

## Facts
- Category: Cards
- Stack: HTML, CSS, and JavaScript
- Added: 2026-08-01
- Canonical page: https://html.bosconvertpro.com/collection/invite-teammates
- Full-page preview: https://html.bosconvertpro.com/collection/invite-teammates/full
- Live demo: https://html.bosconvertpro.com/demos/invite-teammates/index.html
- Markdown mirror: https://html.bosconvertpro.com/collection/invite-teammates/index.md
- License: https://html.bosconvertpro.com/terms

## How to use
1. Open https://html.bosconvertpro.com/collection/invite-teammates/full to preview the animation in full screen.
2. Copy index.html, styles.css, and script.js from the code tabs on https://html.bosconvertpro.com/collection/invite-teammates.
3. Paste the files into your project and adjust timing, easing, and content as needed.

## FAQ
### What is Invite-Teammates?

Invite-Teammates is a cards UI animation from collections.html. A refined invite flow with animated teammate cards — collaboration UI that feels light and intentional.

### What is Invite-Teammates built with?

Invite-Teammates is built with HTML, CSS, and JavaScript. The source for every file is shown on the animation page and can be copied straight into your project.

### How do I use Invite-Teammates in my project?

Open the animation page (https://html.bosconvertpro.com/collection/invite-teammates), preview it live, then copy index.html, styles.css, and script.js from the code tabs. Paste the files into your project and adjust timing, easing, and content as needed. A standalone demo runs at https://html.bosconvertpro.com/demos/invite-teammates/index.html.

### Is Invite-Teammates free to use?

Yes. The Invite-Teammates demo code may be used in personal and commercial projects. You may not resell or redistribute the collection itself as a competing template pack. Full terms: https://html.bosconvertpro.com/terms.

## Source — index.html
````html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Invite Teammates</title>
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
      rel="stylesheet"
    />
    <link rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <div class="page">
      <button type="button" class="page__open" id="openModal" aria-haspopup="dialog">
        Open invite
      </button>

      <div class="overlay is-open" id="overlay" hidden>
        <div
          class="modal"
          id="modal"
          role="dialog"
          aria-modal="true"
          aria-labelledby="modalTitle"
        >
          <!-- Header -->
          <header class="modal__header">
            <h1 class="modal__title" id="modalTitle">Invite Teammates</h1>
            <button type="button" class="icon-btn" id="closeModal" aria-label="Close">
              <svg width="18" height="18" viewBox="0 0 24 24" fill="none" aria-hidden="true">
                <path
                  d="M6 6l12 12M18 6L6 18"
                  stroke="currentColor"
                  stroke-width="2"
                  stroke-linecap="round"
                />
              </svg>
            </button>
          </header>

          <div class="modal__body">
            <!-- Invite field -->
            <div class="invite-row">
              <div class="invite-field" id="inviteField">
                <div class="chips" id="chips"></div>
                <input
                  type="text"
                  class="invite-field__input"
                  id="inviteInput"
                  placeholder="Search name or email"
                  autocomplete="off"
                  aria-autocomplete="list"
                  aria-controls="suggestions"
                  aria-expanded="false"
                />
              </div>
              <button type="button" class="btn btn--primary" id="inviteBtn">Invite</button>
            </div>

            <!-- Suggestions dropdown -->
            <ul class="suggestions" id="suggestions" role="listbox" hidden></ul>

            <!-- Shareable link card -->
            <div class="share-card" id="shareCard">
              <div class="share-card__icon" aria-hidden="true">
                <svg width="20" height="20" viewBox="0 0 24 24" fill="none">
                  <path
                    d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"
                    stroke="currentColor"
                    stroke-width="1.8"
                    stroke-linecap="round"
                    stroke-linejoin="round"
                  />
                  <path
                    d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"
                    stroke="currentColor"
                    stroke-width="1.8"
                    stroke-linecap="round"
                    stroke-linejoin="round"
                  />
                </svg>
                <span class="share-card__dot" id="linkLiveDot"></span>
              </div>
              <div class="share-card__copy">
                <p class="share-card__title" id="shareTitle">Shareable Link is now Live!</p>
                <p class="share-card__desc">Create and get shareable link for this file.</p>
              </div>
              <button type="button" class="btn btn--soft" id="getLinkBtn">Get Link</button>
            </div>

            <!-- Members -->
            <ul class="members" id="members" aria-label="Team members"></ul>
          </div>
        </div>
      </div>
    </div>

    <!-- Toast -->
    <div class="toast" id="toast" role="status" aria-live="polite" hidden></div>

    <!-- Member menu (portal) -->
    <div class="menu" id="memberMenu" hidden role="menu">
      <button type="button" class="menu__item" data-action="message" role="menuitem">
        <span class="menu__icon" aria-hidden="true">
          <svg viewBox="0 0 24 24" fill="none">
            <path
              d="M4.5 6.75A2.25 2.25 0 0 1 6.75 4.5h10.5a2.25 2.25 0 0 1 2.25 2.25v7.5a2.25 2.25 0 0 1-2.25 2.25H9.3l-3.6 2.7a.75.75 0 0 1-1.2-.6V6.75Z"
              stroke="currentColor"
              stroke-width="1.7"
              stroke-linejoin="round"
            />
            <path
              d="M8.25 10h7.5M8.25 13h4.5"
              stroke="currentColor"
              stroke-width="1.7"
              stroke-linecap="round"
            />
          </svg>
        </span>
        <span class="menu__label">Send message</span>
      </button>
      <button type="button" class="menu__item" data-action="role" role="menuitem">
        <span class="menu__icon" aria-hidden="true">
          <svg viewBox="0 0 24 24" fill="none">
            <circle
              cx="12"
              cy="8"
              r="3.25"
              stroke="currentColor"
              stroke-width="1.7"
            />
            <path
              d="M5.5 18.5c.8-3.1 3.3-4.75 6.5-4.75s5.7 1.65 6.5 4.75"
              stroke="currentColor"
              stroke-width="1.7"
              stroke-linecap="round"
            />
            <path
              d="M17.25 8.25h3M18.75 6.75v3"
              stroke="currentColor"
              stroke-width="1.7"
              stroke-linecap="round"
            />
          </svg>
        </span>
        <span class="menu__label">Change role</span>
      </button>
      <button type="button" class="menu__item menu__item--danger" data-action="remove" role="menuitem">
        <span class="menu__icon" aria-hidden="true">
          <svg viewBox="0 0 24 24" fill="none">
            <circle
              cx="10"
              cy="8"
              r="3.25"
              stroke="currentColor"
              stroke-width="1.7"
            />
            <path
              d="M3.5 18.5c.75-3 3-4.75 6.5-4.75 1.15 0 2.2.2 3.1.55"
              stroke="currentColor"
              stroke-width="1.7"
              stroke-linecap="round"
            />
            <path
              d="M16.5 12.5 21 17m0-4.5L16.5 17"
              stroke="currentColor"
              stroke-width="1.7"
              stroke-linecap="round"
            />
          </svg>
        </span>
        <span class="menu__label">Remove access</span>
      </button>
    </div>

    <script src="script.js"></script>
  </body>
</html>
````

## Source — styles.css
````css
/* ============================================
   Invite Teammates — Professional UI
   ============================================ */

:root {
  --bg: #ececec;
  --bg-soft: #f4f4f4;
  --surface: #ffffff;
  --ink: #1a1a1a;
  --ink-2: #3d3d3d;
  --muted: #8a8a8a;
  --line: #ebebeb;
  --line-strong: #e2e2e2;
  --chip: #f0f0f0;
  --chip-hover: #e8e8e8;
  --primary: #1c1c1c;
  --primary-hover: #000000;
  --online: #1f9d55;
  --away: #c4851c;
  --inactive: #d14343;
  --soft-btn: #f7f7f7;
  --shadow-modal: 0 24px 64px rgba(0, 0, 0, 0.1), 0 8px 24px rgba(0, 0, 0, 0.06);
  --shadow-chip: 0 1px 2px rgba(0, 0, 0, 0.04);
  --radius-modal: 20px;
  --radius-pill: 999px;
  --radius-card: 14px;
  --font: "Inter", system-ui, -apple-system, sans-serif;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
}

*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html,
body {
  height: 100%;
}

body {
  font-family: var(--font);
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

button,
input {
  font: inherit;
  color: inherit;
  border: 0;
  background: none;
}

button {
  cursor: pointer;
}

ul {
  list-style: none;
}

/* ---------- Page ---------- */

.page {
  min-height: 100%;
  min-height: 100dvh;
  display: grid;
  place-items: center;
  padding: 2rem 1rem;
  background:
    radial-gradient(ellipse 80% 60% at 50% 40%, #f7f7f7 0%, transparent 70%),
    var(--bg);
}

.page__open {
  padding: 0.85rem 1.4rem;
  border-radius: var(--radius-pill);
  background: var(--primary);
  color: #fff;
  font-weight: 600;
  font-size: 0.95rem;
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.15);
  transition: transform 0.25s var(--ease), background 0.25s var(--ease);
}

.page__open:hover {
  background: var(--primary-hover);
  transform: translateY(-1px);
}

/* ---------- Overlay + Modal ---------- */

.overlay {
  position: fixed;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 1.25rem;
  background: rgba(230, 230, 230, 0.55);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  z-index: 40;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.28s var(--ease), visibility 0.28s var(--ease);
}

.overlay.is-open {
  opacity: 1;
  visibility: visible;
}

.overlay[hidden] {
  display: none;
}

.overlay.is-open[hidden] {
  display: grid;
}

.modal {
  width: min(520px, 100%);
  background: var(--surface);
  border-radius: var(--radius-modal);
  box-shadow: var(--shadow-modal);
  border: 1px solid rgba(0, 0, 0, 0.04);
  overflow: visible;
  transform: translateY(12px) scale(0.98);
  opacity: 0;
  transition: transform 0.35s var(--ease), opacity 0.35s var(--ease);
}

.overlay.is-open .modal {
  transform: translateY(0) scale(1);
  opacity: 1;
}

.modal__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1.15rem 1.25rem 1rem;
  border-bottom: 1px solid var(--line);
}

.modal__title {
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--ink);
}

.icon-btn {
  width: 32px;
  height: 32px;
  border-radius: 10px;
  display: grid;
  place-items: center;
  color: var(--muted);
  transition: background 0.2s var(--ease), color 0.2s var(--ease);
}

.icon-btn:hover {
  background: var(--chip);
  color: var(--ink);
}

.modal__body {
  position: relative;
  padding: 1rem 1.25rem 1.15rem;
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
}

/* ---------- Invite row ---------- */

.invite-row {
  display: flex;
  align-items: center;
  gap: 0.65rem;
}

.invite-field {
  flex: 1;
  min-height: 48px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  padding: 0.35rem 0.45rem 0.35rem 0.4rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-pill);
  background: #fff;
  transition: border-color 0.2s var(--ease), box-shadow 0.2s var(--ease);
}

.invite-field:focus-within {
  border-color: #cfcfcf;
  box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.04);
}

.chips {
  display: contents;
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.28rem 0.45rem 0.28rem 0.28rem;
  border-radius: var(--radius-pill);
  background: var(--chip);
  box-shadow: var(--shadow-chip);
  max-width: 100%;
  animation: chip-in 0.28s var(--ease);
}

@keyframes chip-in {
  from {
    opacity: 0;
    transform: scale(0.92);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

.chip__avatar {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
  background: #ddd;
}

.chip__name {
  font-size: 0.84rem;
  font-weight: 500;
  color: var(--ink-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 9.5rem;
}

.chip__remove {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  color: var(--muted);
  flex-shrink: 0;
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}

.chip__remove:hover {
  background: #e0e0e0;
  color: var(--ink);
}

.chip__remove svg {
  width: 10px;
  height: 10px;
}

.invite-field__input {
  flex: 1;
  min-width: 5.5rem;
  height: 34px;
  padding: 0 0.55rem;
  font-size: 0.9rem;
  outline: none;
  color: var(--ink);
}

.invite-field__input::placeholder {
  color: #b0b0b0;
}

/* ---------- Buttons ---------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 44px;
  padding: 0 1.15rem;
  border-radius: var(--radius-pill);
  font-size: 0.9rem;
  font-weight: 600;
  white-space: nowrap;
  transition:
    background 0.2s var(--ease),
    color 0.2s var(--ease),
    transform 0.2s var(--ease),
    box-shadow 0.2s var(--ease);
}

.btn--primary {
  background: var(--primary);
  color: #fff;
  min-width: 88px;
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
}

.btn--primary:hover:not(:disabled) {
  background: var(--primary-hover);
  transform: translateY(-1px);
}

.btn--primary:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  box-shadow: none;
}

.btn--soft {
  height: 36px;
  padding: 0 0.95rem;
  background: var(--surface);
  border: 1px solid var(--line-strong);
  color: var(--ink-2);
  font-weight: 500;
  font-size: 0.84rem;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
}

.btn--soft:hover {
  background: var(--soft-btn);
}

.btn--soft.is-copied {
  color: var(--online);
  border-color: #c8ebd6;
  background: #f3fbf6;
}

/* ---------- Suggestions ---------- */

.suggestions {
  position: absolute;
  left: 1.25rem;
  right: 1.25rem;
  top: 4.35rem;
  z-index: 20;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow: 0 16px 40px rgba(0, 0, 0, 0.1);
  max-height: 220px;
  overflow: auto;
  padding: 0.35rem;
  transform-origin: top center;
  opacity: 0;
  transform: translateY(-6px) scale(0.98);
  pointer-events: none;
  transition:
    opacity 0.22s var(--ease),
    transform 0.28s var(--ease),
    visibility 0.28s;
  visibility: hidden;
}

.suggestions.is-open {
  opacity: 1;
  transform: translateY(0) scale(1);
  pointer-events: auto;
  visibility: visible;
}

.suggestions[hidden] {
  display: none;
}

.suggestions.is-open[hidden] {
  display: block;
}

.suggestions__item {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  width: 100%;
  padding: 0.55rem 0.65rem;
  border-radius: 10px;
  text-align: left;
  transition: background 0.15s var(--ease);
}

.suggestions__item:hover,
.suggestions__item.is-active {
  background: var(--chip);
}

.suggestions__avatar {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  object-fit: cover;
  background: #ddd;
}

.suggestions__meta {
  min-width: 0;
}

.suggestions__name {
  font-size: 0.9rem;
  font-weight: 560;
  color: var(--ink);
}

.suggestions__email {
  font-size: 0.78rem;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.suggestions__empty {
  padding: 0.85rem 0.75rem;
  font-size: 0.85rem;
  color: var(--muted);
  text-align: center;
}

/* ---------- Share card ---------- */

.share-card {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem 0.8rem;
  border-radius: var(--radius-card);
  background: #f5f5f5;
  border: 1px solid transparent;
}

.share-card__icon {
  position: relative;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  background: #fff;
  border: 1px solid var(--line);
  display: grid;
  place-items: center;
  color: #6a6a6a;
  flex-shrink: 0;
}

.share-card__dot {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--inactive);
  border: 2px solid #f5f5f5;
  box-sizing: content-box;
}

.share-card.is-live .share-card__dot {
  background: var(--online);
}

.share-card__copy {
  flex: 1;
  min-width: 0;
}

.share-card__title {
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--ink);
  letter-spacing: -0.01em;
}

.share-card__desc {
  margin-top: 0.15rem;
  font-size: 0.78rem;
  color: var(--muted);
  line-height: 1.35;
}

/* ---------- Members ---------- */

.members {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  margin-top: 0.15rem;
}

.member {
  display: grid;
  grid-template-columns: auto 1fr auto auto;
  align-items: center;
  gap: 0.75rem;
  padding: 0.65rem 0.2rem;
  border-radius: 12px;
  transition: background 0.15s var(--ease);
}

.member:hover {
  background: #fafafa;
}

.member__avatar-wrap {
  position: relative;
  width: 40px;
  height: 40px;
  flex-shrink: 0;
}

.member__avatar {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  object-fit: cover;
  background: #e5e5e5;
  display: block;
}

.member__info {
  min-width: 0;
}

.member__name {
  font-size: 0.92rem;
  font-weight: 600;
  color: var(--ink);
  letter-spacing: -0.01em;
}

.member__email {
  margin-top: 0.12rem;
  font-size: 0.78rem;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.member__status {
  font-size: 0.8rem;
  font-weight: 500;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  justify-self: end;
}

.member__status--online {
  color: var(--online);
}

.member__status--away {
  color: var(--away);
}

.member__status--inactive {
  color: var(--inactive);
}

.member__status-icon {
  width: 14px;
  height: 14px;
  display: inline-grid;
  place-items: center;
}

.member__more {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 1px solid var(--line);
  display: grid;
  place-items: center;
  color: #9a9a9a;
  background: #fff;
  transition: background 0.15s var(--ease), border-color 0.15s var(--ease), color 0.15s var(--ease);
}

.member__more:hover,
.member__more[aria-expanded="true"] {
  background: var(--chip);
  border-color: #ddd;
  color: var(--ink);
}

/* ---------- Context menu ---------- */

.menu {
  position: fixed;
  z-index: 60;
  min-width: 168px;
  padding: 0.35rem;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: 0 14px 36px rgba(0, 0, 0, 0.12);
  transform-origin: top right;
  opacity: 0;
  transform: translateY(-8px) scale(0.96);
  pointer-events: none;
  visibility: hidden;
  transition:
    opacity 0.2s var(--ease),
    transform 0.26s var(--ease),
    visibility 0.26s;
  will-change: opacity, transform;
}

.menu.is-open {
  opacity: 1;
  transform: translateY(0) scale(1);
  pointer-events: auto;
  visibility: visible;
}

.menu[hidden] {
  display: none;
}

.menu.is-open[hidden] {
  display: block;
}

.menu.is-open .menu__item {
  animation: menu-item-in 0.32s var(--ease) both;
}

.menu.is-open .menu__item:nth-child(1) {
  animation-delay: 0.02s;
}

.menu.is-open .menu__item:nth-child(2) {
  animation-delay: 0.05s;
}

.menu.is-open .menu__item:nth-child(3) {
  animation-delay: 0.08s;
}

@keyframes menu-item-in {
  from {
    opacity: 0;
    transform: translateY(4px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.menu__item {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  width: 100%;
  text-align: left;
  padding: 0.6rem 0.7rem;
  border-radius: 9px;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--ink-2);
  transition:
    background 0.15s var(--ease),
    color 0.15s var(--ease);
}

.menu__item:hover {
  background: var(--chip);
  color: var(--ink);
}

.menu__icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  color: #6b6b6b;
  transition: color 0.15s var(--ease);
}

.menu__icon svg {
  width: 18px;
  height: 18px;
  display: block;
}

.menu__label {
  flex: 1;
  line-height: 1.2;
}

.menu__item:hover .menu__icon {
  color: var(--ink);
}

.menu__item--danger {
  color: var(--inactive);
}

.menu__item--danger .menu__icon {
  color: var(--inactive);
}

.menu__item--danger:hover {
  background: #fdf2f2;
  color: #b42318;
}

.menu__item--danger:hover .menu__icon {
  color: #b42318;
}

/* ---------- Toast ---------- */

.toast {
  position: fixed;
  left: 50%;
  bottom: 1.5rem;
  transform: translateX(-50%) translateY(12px);
  z-index: 80;
  padding: 0.7rem 1.1rem;
  border-radius: var(--radius-pill);
  background: #1c1c1c;
  color: #fff;
  font-size: 0.86rem;
  font-weight: 500;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.2);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.28s var(--ease), transform 0.28s var(--ease);
}

.toast.is-show {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

.toast[hidden] {
  display: none;
}

.toast.is-show[hidden] {
  display: block;
}

/* ---------- Responsive ---------- */

@media (max-width: 560px) {
  .modal {
    border-radius: 16px;
  }

  .modal__header,
  .modal__body {
    padding-left: 1rem;
    padding-right: 1rem;
  }

  .invite-row {
    flex-direction: column;
    align-items: stretch;
  }

  .btn--primary {
    width: 100%;
  }

  .share-card {
    flex-wrap: wrap;
  }

  .share-card .btn--soft {
    margin-left: auto;
  }

  .member {
    grid-template-columns: auto 1fr auto;
    grid-template-areas:
      "avatar info more"
      "avatar status more";
    gap: 0.2rem 0.7rem;
  }

  .member__avatar-wrap {
    grid-area: avatar;
  }

  .member__info {
    grid-area: info;
  }

  .member__status {
    grid-area: status;
    justify-self: start;
  }

  .member__more {
    grid-area: more;
  }
}
````

## Source — script.js
````javascript
/**
 * Invite Teammates — interactive modal
 * Chip multi-select, shareable link, member list + menu
 */

(() => {
  "use strict";

  /* ---------- Data ---------- */

  const DIRECTORY = [
    {
      id: "richard",
      name: "Richard Winson",
      email: "richardwinson@company.com",
      avatar:
        "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&w=120&q=80",
    },
    {
      id: "tedd",
      name: "Tedd Brooks",
      email: "teddbrooks@company.com",
      avatar:
        "https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=crop&w=120&q=80",
    },
    {
      id: "maya",
      name: "Maya Chen",
      email: "mayachen@company.com",
      avatar:
        "https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&w=120&q=80",
    },
    {
      id: "sam",
      name: "Sam Okonkwo",
      email: "samokonkwo@company.com",
      avatar:
        "https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?auto=format&fit=crop&w=120&q=80",
    },
    {
      id: "elena",
      name: "Elena Vos",
      email: "elenavos@company.com",
      avatar:
        "https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=120&q=80",
    },
  ];

  const MEMBERS = [
    {
      id: "arlo",
      name: "Arlo Finch",
      email: "arlofinch@company.com",
      avatar:
        "https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?auto=format&fit=crop&w=120&q=80",
      status: "online",
      statusLabel: "Online",
    },
    {
      id: "juniper",
      name: "Juniper Lane",
      email: "juniperlane@company.com",
      avatar:
        "https://images.unsplash.com/photo-1544005313-94ddf0286df2?auto=format&fit=crop&w=120&q=80",
      status: "away",
      statusLabel: "Away (2 mins)",
    },
    {
      id: "rowan",
      name: "Rowan Sage",
      email: "rowansage@company.com",
      avatar:
        "https://images.unsplash.com/photo-1506277886164-e25aa3f4ef7f?auto=format&fit=crop&w=120&q=80",
      status: "away",
      statusLabel: "Away (2 days)",
    },
    {
      id: "finnian",
      name: "Finnian York",
      email: "finnianyork@company.com",
      avatar:
        "https://images.unsplash.com/photo-1519085360753-af0119f7cbe7?auto=format&fit=crop&w=120&q=80",
      status: "inactive",
      statusLabel: "Inactive (1 year)",
    },
  ];

  const SHARE_URL = "https://app.example.com/s/invite/team-7f3a9c";

  /* ---------- DOM ---------- */

  const openBtn = document.getElementById("openModal");
  const overlay = document.getElementById("overlay");
  const modal = document.getElementById("modal");
  const closeBtn = document.getElementById("closeModal");
  const chipsEl = document.getElementById("chips");
  const inviteField = document.getElementById("inviteField");
  const inviteInput = document.getElementById("inviteInput");
  const inviteBtn = document.getElementById("inviteBtn");
  const suggestionsEl = document.getElementById("suggestions");
  const membersEl = document.getElementById("members");
  const shareCard = document.getElementById("shareCard");
  const shareTitle = document.getElementById("shareTitle");
  const getLinkBtn = document.getElementById("getLinkBtn");
  const toast = document.getElementById("toast");
  const memberMenu = document.getElementById("memberMenu");

  /** @type {typeof DIRECTORY} */
  let selected = [
    DIRECTORY.find((p) => p.id === "richard"),
  ].filter(Boolean);

  let members = [...MEMBERS];
  let linkLive = true;
  let activeSuggestion = -1;
  let menuMemberId = null;
  let toastTimer = null;

  /* ---------- Helpers ---------- */

  function escapeHtml(str) {
    return String(str)
      .replace(/&/g, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;")
      .replace(/"/g, "&quot;");
  }

  function showToast(message) {
    toast.hidden = false;
    toast.textContent = message;
    requestAnimationFrame(() => toast.classList.add("is-show"));
    clearTimeout(toastTimer);
    toastTimer = setTimeout(() => {
      toast.classList.remove("is-show");
      setTimeout(() => {
        toast.hidden = true;
      }, 280);
    }, 2200);
  }

  function isSelected(id) {
    return selected.some((p) => p.id === id);
  }

  function isAlreadyMember(email) {
    return members.some((m) => m.email.toLowerCase() === email.toLowerCase());
  }

  /* ---------- Modal open / close ---------- */

  function openModal() {
    overlay.hidden = false;
    requestAnimationFrame(() => overlay.classList.add("is-open"));
    document.body.style.overflow = "hidden";
    setTimeout(() => inviteInput.focus(), 50);
  }

  function closeModal() {
    overlay.classList.remove("is-open");
    hideSuggestions();
    closeMemberMenu();
    document.body.style.overflow = "";
    setTimeout(() => {
      if (!overlay.classList.contains("is-open")) overlay.hidden = true;
    }, 280);
  }

  /* ---------- Chips ---------- */

  function renderChips() {
    chipsEl.innerHTML = selected
      .map(
        (person) => `
      <span class="chip" data-id="${escapeHtml(person.id)}">
        <img class="chip__avatar" src="${escapeHtml(person.avatar)}" alt="" />
        <span class="chip__name">${escapeHtml(person.name)}</span>
        <button type="button" class="chip__remove" data-remove="${escapeHtml(person.id)}" aria-label="Remove ${escapeHtml(person.name)}">
          <svg viewBox="0 0 12 12" fill="none" aria-hidden="true">
            <path d="M3 3l6 6M9 3L3 9" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/>
          </svg>
        </button>
      </span>`
      )
      .join("");

    inviteBtn.disabled = selected.length === 0;
  }

  function addPerson(person) {
    if (!person || isSelected(person.id)) return;
    selected.push(person);
    renderChips();
    inviteInput.value = "";
    hideSuggestions();
    inviteInput.focus();
  }

  function removePerson(id) {
    selected = selected.filter((p) => p.id !== id);
    renderChips();
    inviteInput.focus();
  }

  /* ---------- Suggestions ---------- */

  function getMatches(query) {
    const q = query.trim().toLowerCase();
    if (!q) return [];
    return DIRECTORY.filter(
      (p) =>
        !isSelected(p.id) &&
        (p.name.toLowerCase().includes(q) || p.email.toLowerCase().includes(q))
    ).slice(0, 6);
  }

  let suggestionsCloseTimer = null;

  function showSuggestionsPanel() {
    clearTimeout(suggestionsCloseTimer);
    suggestionsEl.hidden = false;
    // Force reflow so transition runs
    void suggestionsEl.offsetWidth;
    requestAnimationFrame(() => {
      suggestionsEl.classList.add("is-open");
    });
    inviteInput.setAttribute("aria-expanded", "true");
  }

  function renderSuggestions(matches) {
    if (!matches.length) {
      const q = inviteInput.value.trim();
      if (!q) {
        hideSuggestions();
        return;
      }
      suggestionsEl.innerHTML = `<li class="suggestions__empty">No people found for “${escapeHtml(q)}”</li>`;
      showSuggestionsPanel();
      return;
    }

    activeSuggestion = 0;
    suggestionsEl.innerHTML = matches
      .map(
        (p, i) => `
      <li>
        <button type="button" class="suggestions__item${i === 0 ? " is-active" : ""}" role="option" data-id="${escapeHtml(p.id)}">
          <img class="suggestions__avatar" src="${escapeHtml(p.avatar)}" alt="" />
          <span class="suggestions__meta">
            <span class="suggestions__name">${escapeHtml(p.name)}</span>
            <span class="suggestions__email">${escapeHtml(p.email)}</span>
          </span>
        </button>
      </li>`
      )
      .join("");
    showSuggestionsPanel();
  }

  function hideSuggestions() {
    suggestionsEl.classList.remove("is-open");
    activeSuggestion = -1;
    inviteInput.setAttribute("aria-expanded", "false");
    clearTimeout(suggestionsCloseTimer);
    suggestionsCloseTimer = setTimeout(() => {
      if (!suggestionsEl.classList.contains("is-open")) {
        suggestionsEl.hidden = true;
        suggestionsEl.innerHTML = "";
      }
    }, 260);
  }

  function updateSuggestionHighlight() {
    const items = [...suggestionsEl.querySelectorAll(".suggestions__item")];
    items.forEach((el, i) => el.classList.toggle("is-active", i === activeSuggestion));
    items[activeSuggestion]?.scrollIntoView({ block: "nearest" });
  }

  /* ---------- Members list ---------- */

  function statusClass(status) {
    if (status === "online") return "member__status--online";
    if (status === "away") return "member__status--away";
    return "member__status--inactive";
  }

  function statusIcon(status) {
    if (status !== "inactive") return "";
    return `<span class="member__status-icon" aria-hidden="true">
      <svg width="12" height="12" viewBox="0 0 16 16" fill="none">
        <circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.4"/>
        <path d="M8 4.5v5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
        <circle cx="8" cy="11.5" r="0.8" fill="currentColor"/>
      </svg>
    </span>`;
  }

  function renderMembers() {
    membersEl.innerHTML = members
      .map(
        (m) => `
      <li class="member" data-id="${escapeHtml(m.id)}">
        <div class="member__avatar-wrap">
          <img class="member__avatar" src="${escapeHtml(m.avatar)}" alt="" />
        </div>
        <div class="member__info">
          <div class="member__name">${escapeHtml(m.name)}</div>
          <div class="member__email">${escapeHtml(m.email)}</div>
        </div>
        <div class="member__status ${statusClass(m.status)}">
          ${statusIcon(m.status)}
          <span>${escapeHtml(m.statusLabel)}</span>
        </div>
        <button
          type="button"
          class="member__more"
          data-menu="${escapeHtml(m.id)}"
          aria-label="More options for ${escapeHtml(m.name)}"
          aria-haspopup="menu"
          aria-expanded="false"
        >
          <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
            <circle cx="8" cy="3.5" r="1.3"/>
            <circle cx="8" cy="8" r="1.3"/>
            <circle cx="8" cy="12.5" r="1.3"/>
          </svg>
        </button>
      </li>`
      )
      .join("");
  }

  /* ---------- Share link ---------- */

  function renderShareCard() {
    shareCard.classList.toggle("is-live", linkLive);
    shareTitle.textContent = linkLive
      ? "Shareable Link is now Live!"
      : "Shareable Link is off";
  }

  async function copyLink() {
    try {
      await navigator.clipboard.writeText(SHARE_URL);
    } catch {
      /* fallback */
      const ta = document.createElement("textarea");
      ta.value = SHARE_URL;
      document.body.appendChild(ta);
      ta.select();
      document.execCommand("copy");
      ta.remove();
    }
    getLinkBtn.textContent = "Copied!";
    getLinkBtn.classList.add("is-copied");
    showToast("Shareable link copied");
    setTimeout(() => {
      getLinkBtn.textContent = "Get Link";
      getLinkBtn.classList.remove("is-copied");
    }, 1800);
  }

  /* ---------- Invite action ---------- */

  function inviteSelected() {
    if (!selected.length) return;

    const invited = [];
    selected.forEach((person) => {
      if (isAlreadyMember(person.email)) return;
      members = [
        {
          id: person.id,
          name: person.name,
          email: person.email,
          avatar: person.avatar,
          status: "online",
          statusLabel: "Online",
        },
        ...members,
      ];
      invited.push(person.name);
    });

    const count = selected.length;
    selected = [];
    renderChips();
    renderMembers();
    hideSuggestions();

    if (invited.length) {
      showToast(
        invited.length === 1
          ? `Invited ${invited[0]}`
          : `Invited ${invited.length} teammates`
      );
    } else {
      showToast(
        count === 1 ? "Already on the team" : "Selected people are already members"
      );
    }
  }

  /* ---------- Member menu ---------- */

  let menuCloseTimer = null;

  function openMemberMenu(btn, memberId) {
    clearTimeout(menuCloseTimer);
    menuMemberId = memberId;

    const rect = btn.getBoundingClientRect();
    const menuW = 168;
    const menuH = 130;
    let left = rect.right - menuW;
    let top = rect.bottom + 6;
    let origin = "top right";

    if (left < 8) left = 8;
    if (top + menuH > window.innerHeight - 8) {
      top = rect.top - menuH - 6;
      origin = "bottom right";
    }

    memberMenu.style.left = `${left}px`;
    memberMenu.style.top = `${top}px`;
    memberMenu.style.transformOrigin = origin;

    // If already open for another row, reset animation cleanly
    memberMenu.classList.remove("is-open");
    memberMenu.hidden = false;
    void memberMenu.offsetWidth;

    requestAnimationFrame(() => {
      memberMenu.classList.add("is-open");
    });

    document.querySelectorAll(".member__more").forEach((el) => {
      el.setAttribute("aria-expanded", el === btn ? "true" : "false");
    });
  }

  function closeMemberMenu() {
    menuMemberId = null;
    memberMenu.classList.remove("is-open");
    document.querySelectorAll(".member__more").forEach((el) => {
      el.setAttribute("aria-expanded", "false");
    });

    clearTimeout(menuCloseTimer);
    menuCloseTimer = setTimeout(() => {
      if (!memberMenu.classList.contains("is-open")) {
        memberMenu.hidden = true;
      }
    }, 260);
  }

  function isMenuOpen() {
    return memberMenu.classList.contains("is-open");
  }

  function isSuggestionsOpen() {
    return suggestionsEl.classList.contains("is-open");
  }

  function handleMenuAction(action) {
    const member = members.find((m) => m.id === menuMemberId);
    if (!member) {
      closeMemberMenu();
      return;
    }

    if (action === "message") {
      showToast(`Message draft for ${member.name}`);
    } else if (action === "role") {
      showToast(`Role editor for ${member.name}`);
    } else if (action === "remove") {
      members = members.filter((m) => m.id !== member.id);
      renderMembers();
      showToast(`${member.name} removed`);
    }
    closeMemberMenu();
  }

  /* ---------- Events ---------- */

  function bindEvents() {
    openBtn.addEventListener("click", openModal);
    closeBtn.addEventListener("click", closeModal);

    overlay.addEventListener("click", (e) => {
      if (e.target === overlay) closeModal();
    });

    document.addEventListener("keydown", (e) => {
      if (e.key === "Escape") {
        if (isMenuOpen()) {
          closeMemberMenu();
          return;
        }
        if (isSuggestionsOpen()) {
          hideSuggestions();
          return;
        }
        if (overlay.classList.contains("is-open")) closeModal();
      }
    });

    inviteField.addEventListener("click", (e) => {
      if (e.target.closest(".chip__remove")) return;
      inviteInput.focus();
    });

    chipsEl.addEventListener("click", (e) => {
      const btn = e.target.closest("[data-remove]");
      if (!btn) return;
      removePerson(btn.getAttribute("data-remove"));
    });

    inviteInput.addEventListener("input", () => {
      const matches = getMatches(inviteInput.value);
      renderSuggestions(matches);
    });

    inviteInput.addEventListener("keydown", (e) => {
      const items = [...suggestionsEl.querySelectorAll(".suggestions__item")];
      const listOpen = isSuggestionsOpen() && items.length;

      if (e.key === "ArrowDown" && listOpen) {
        e.preventDefault();
        activeSuggestion = (activeSuggestion + 1) % items.length;
        updateSuggestionHighlight();
        return;
      }

      if (e.key === "ArrowUp" && listOpen) {
        e.preventDefault();
        activeSuggestion = (activeSuggestion - 1 + items.length) % items.length;
        updateSuggestionHighlight();
        return;
      }

      if (e.key === "Enter") {
        e.preventDefault();
        if (listOpen && items[activeSuggestion]) {
          const id = items[activeSuggestion].getAttribute("data-id");
          const person = DIRECTORY.find((p) => p.id === id);
          addPerson(person);
          return;
        }
        inviteSelected();
        return;
      }

      if (e.key === "Backspace" && !inviteInput.value && selected.length) {
        removePerson(selected[selected.length - 1].id);
      }
    });

    suggestionsEl.addEventListener("click", (e) => {
      const item = e.target.closest("[data-id]");
      if (!item) return;
      const person = DIRECTORY.find((p) => p.id === item.getAttribute("data-id"));
      addPerson(person);
    });

    inviteBtn.addEventListener("click", inviteSelected);

    getLinkBtn.addEventListener("click", () => {
      if (!linkLive) {
        linkLive = true;
        renderShareCard();
      }
      copyLink();
    });

    // Toggle live state on icon click (nice extra)
    shareCard.querySelector(".share-card__icon")?.addEventListener("click", () => {
      linkLive = !linkLive;
      renderShareCard();
      showToast(linkLive ? "Shareable link is live" : "Shareable link turned off");
    });

    membersEl.addEventListener("click", (e) => {
      const btn = e.target.closest("[data-menu]");
      if (!btn) return;
      e.stopPropagation();
      const id = btn.getAttribute("data-menu");
      if (menuMemberId === id && isMenuOpen()) {
        closeMemberMenu();
      } else {
        openMemberMenu(btn, id);
      }
    });

    memberMenu.addEventListener("click", (e) => {
      const item = e.target.closest("[data-action]");
      if (!item) return;
      handleMenuAction(item.getAttribute("data-action"));
    });

    document.addEventListener("click", (e) => {
      if (
        isMenuOpen() &&
        !e.target.closest("#memberMenu") &&
        !e.target.closest("[data-menu]")
      ) {
        closeMemberMenu();
      }
      if (
        isSuggestionsOpen() &&
        !e.target.closest("#suggestions") &&
        !e.target.closest("#inviteField")
      ) {
        hideSuggestions();
      }
    });
  }

  /* ---------- Init ---------- */

  function init() {
    renderChips();
    renderMembers();
    renderShareCard();
    bindEvents();
    // Demo opens visible (matches screenshot presentation)
    openModal();
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", init);
  } else {
    init();
  }
})();
````
