> ## Documentation Index
> Fetch the complete documentation index at: https://dev.almedia.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Best practices

> The patterns behind a high install-to-link rate and a clear LTV lift for linked users — pop-up timing and design, the homescreen entry point, Rewarded Progression, the Earn Tab and a pre-launch checklist.

export const ICON_CHECK = <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
    <path d="M20 6L9 17l-5-5" />
  </svg>;

export const ICON_CROSS = <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
    <path d="M18 6L6 18M6 6l12 12" />
  </svg>;

export const HOTSPOTS = {
  "popup-do-tight.webp": [{
    x: 19,
    y: 73.9,
    type: "good",
    len: 90,
    short: "Freecash icon",
    label: "Freecash branding used",
    note: "The Freecash mark sits next to the sign-up bonus, so players recognise the offer instead of guessing what they are linking to."
  }, {
    x: 92,
    y: 38.9,
    type: "good",
    short: "Currency reward",
    label: "In-game currency reward listed first",
    note: "The in-game coin reward is spelled out at the top of the list, so the value is visible before the player decides."
  }, {
    x: 92,
    y: 73.3,
    type: "good",
    short: "$10 bonus",
    label: "$10 bonus stated outright",
    note: "The $10 sign-up bonus is stated outright rather than hidden behind the flow, which is the single most impactful element of the pop-up."
  }, {
    x: 20,
    y: 86.7,
    type: "good",
    len: 90,
    short: "One CTA",
    label: "One clear CTA focused on Rewards",
    note: "One reward-led button, no competing 'Sign Up' or 'Check Link' action. A single clear CTA outperforms a cluttered pair."
  }, {
    x: 87,
    y: 93.0,
    type: "good",
    short: "Social proof",
    label: "Social proof line",
    note: "A short trust line under the button reassures the player that other people already earn with this."
  }, {
    x: 90,
    y: 9.7,
    type: "good",
    len: 26,
    short: "5s delay",
    label: "Dismissal blocked for 5 seconds",
    note: "A 5 second countdown sits next to a greyed-out close button, so the player reads the reward before they can reflex-close the pop-up."
  }, {
    x: 14,
    y: 43.7,
    type: "good",
    len: 90,
    short: "Owns the screen",
    label: "Covers most of the screen, gameplay dimmed behind it",
    note: "The pop-up does not have to be full screen. Half to three quarters of the screen is enough, as long as it clearly owns the moment and the game behind it is dimmed out."
  }],
  "popup-dont.png": [{
    x: 16,
    y: 27,
    type: "bad",
    len: 100,
    short: "Too small",
    label: "Small card over barely dimmed gameplay",
    note: "It does not have to be full screen, but this card is tiny and the level behind it is barely dimmed, so the game keeps competing for attention and the offer reads as an ad the player swipes away."
  }, {
    x: 88,
    y: 26.5,
    type: "bad",
    short: "Instant close",
    label: "Close button available immediately",
    note: "Nothing holds the pop-up open, so most players tap the X before they read a single reward."
  }, {
    x: 92,
    y: 49,
    type: "bad",
    short: "No value",
    label: "No reward value stated",
    note: "'Coins for playing' and 'Cash bonus' say nothing concrete. No coin amount, no $10 sign-up bonus, so the player has to guess what linking is worth."
  }, {
    x: 20,
    y: 64,
    type: "bad",
    len: 90,
    short: "Sign Up",
    label: "'Sign Up' wording on the CTA",
    note: "'Sign Up' reads as account admin, not reward. Reward-led wording like 'Earn Rewards' converts far better."
  }, {
    x: 20,
    y: 71.5,
    type: "bad",
    len: 90,
    short: "Two CTAs",
    label: "Competing 'Check Link' button",
    note: "A second action splits the decision. One clear reward CTA outperforms a pair every time."
  }, {
    x: 88,
    y: 77.5,
    type: "bad",
    short: "No proof",
    label: "No social proof line",
    note: "Nothing reassures the player that others already earn with this, so an unfamiliar brand gets dismissed."
  }],
  "icon-rewards-fab-v2.webp": [{
    x: 92,
    y: 40,
    type: "good",
    short: "Reward signal",
    label: "Reward signal on the icon",
    note: "Coins on the icon signal a reward sits behind it, so it does not read as another shop button."
  }, {
    x: 92,
    y: 85,
    type: "good",
    short: "Rewards label",
    label: "Rewards label",
    note: "A short 'Rewards' label keeps the purpose obvious for anyone who has not linked yet."
  }],
  "icon-rewards-b.png": [{
    x: 90,
    y: 14,
    type: "good",
    len: 40,
    short: "Notification dot",
    label: "Red '1' notification badge",
    note: "The red badge pulls the eye to the icon the same way an unread message does, so players who have not linked yet keep noticing the entry point."
  }, {
    x: 92,
    y: 45,
    type: "good",
    short: "Cash art",
    label: "Cash and coins reward art",
    note: "Cash and coins carry the reward cue instantly, so the icon never reads as another shop button."
  }, {
    x: 92,
    y: 85,
    type: "good",
    short: "Rewards label",
    label: "Rewards label",
    note: "The label keeps the entry point readable at icon size, where art alone can be ambiguous."
  }],
  "icon-dont-a.svg": [{
    x: 92,
    y: 35,
    type: "bad",
    short: "Logo only",
    label: "Bare logo, no reward art",
    note: "The plain mark carries no cash, coin or gift cue, so nothing tells the player a reward sits behind the icon."
  }, {
    x: 92,
    y: 80,
    type: "bad",
    short: "No label",
    label: "No label under the icon",
    note: "Without a short 'Rewards' label, a player who has not linked yet has no idea what the button does, so they never tap it."
  }],
  "icon-dont-b.svg": [{
    x: 92,
    y: 35,
    type: "bad",
    short: "App logo",
    label: "App logo used as the entry point",
    note: "A bare brand logo reads as a partner badge or another shop button, not as an earning opportunity."
  }, {
    x: 92,
    y: 80,
    type: "bad",
    short: "No signal",
    label: "No reward signal or attention cue",
    note: "No badge, no notification dot, no colour highlight. It blends into the HUD and gets ignored, so the long tail of links never happens."
  }],
  "earn-popup-do.webp": [{
    x: 20.2,
    y: 21.9,
    type: "good",
    len: 90,
    short: "Freecash mark",
    label: "Freecash logo present",
    note: "The Freecash mark sits at the top, so the player recognises where the offers come from and trusts the payout."
  }, {
    x: 78,
    y: 29.4,
    type: "good",
    len: 60,
    short: "Clear promise",
    label: "One clear headline promise",
    note: "'More ways to earn more' states the point of the Earn Tab in one line, before any explanation."
  }, {
    x: 72,
    y: 52.8,
    type: "good",
    len: 50,
    short: "Milestone step",
    label: "The milestone is explained",
    note: "A progress bar and 'reach the goal, usually just a level or two' show how short the path to the reward is."
  }, {
    x: 34,
    y: 60.5,
    type: "good",
    len: 80,
    short: "Exact reward",
    label: "Exact reward stated",
    note: "'Get 300 gems instantly' names the in-game payout and says it lands automatically. Set the real per-game value here."
  }, {
    x: 22,
    y: 74.5,
    type: "good",
    len: 90,
    short: "One CTA",
    label: "One reward-led CTA",
    note: "'Earn Rewards' is the only button, and there is no sign-up wording: the player is already linked, so there is no friction to imply."
  }, {
    x: 80,
    y: 78.8,
    type: "good",
    len: 40,
    short: "Social proof",
    label: "Trust line under the CTA",
    note: "One short social-proof line closes the decision without adding a wall of copy."
  }],
  "earn-popup-dont.png": [{
    x: 88,
    y: 13.5,
    type: "bad",
    len: 40,
    short: "Not dimmed",
    label: "Gameplay behind the pop-up is not dimmed",
    note: "The level, the HUD and the boosters stay at full brightness, so the background keeps competing with the offer and the pop-up reads as a banner the player swipes away."
  }, {
    x: 8,
    y: 33,
    type: "bad",
    len: 60,
    short: "Too small",
    label: "Small card, no dedicated moment",
    note: "Half to three quarters of the screen is enough, but this card barely owns a third of it, so the Earn Tab never gets a launch moment of its own."
  }, {
    x: 30,
    y: 37,
    type: "bad",
    len: 70,
    short: "No logo",
    label: "Freecash logo missing",
    note: "Without the Freecash mark the offer looks like an unknown third party, so the player has no reason to trust the payout."
  }, {
    x: 80,
    y: 50.8,
    type: "bad",
    len: 95,
    short: "Vague step",
    label: "No milestone info",
    note: "'Play the game and see what happens' tells the player nothing about what to reach or how long it takes."
  }, {
    x: 26,
    y: 57.6,
    type: "bad",
    len: 110,
    short: "No reward",
    label: "No exact reward stated anywhere",
    note: "'Earn some gems for your effort' makes the player guess the value, and a guessed reward gets dismissed."
  }, {
    x: 24,
    y: 62.8,
    type: "bad",
    len: 90,
    short: "Sign up",
    label: "\"Sign Up Now\" wording",
    note: "The player is already linked, so sign-up wording invents friction that does not exist and kills the tap."
  }]
};

export const spotsFor = src => HOTSPOTS[String(src).split("/").pop()] || [];

export const Marker = ({spot, n, active, canHover, onActivate, onDismiss}) => {
  const isGood = spot.type === "good";
  const flip = spot.x < 50;
  if (!canHover) {
    return <button type="button" className={"bp-hs-pin" + (isGood ? " bp-hs-pin-good" : "") + (active ? " bp-hs-pin-active" : "")} style={{
      left: spot.x + "%",
      top: spot.y + "%"
    }} aria-label={"Note " + n + ": " + spot.label} onClick={e => {
      e.stopPropagation();
      active ? onDismiss() : onActivate();
    }}>
        {n}
      </button>;
  }
  return <div className={"bp-hs" + (flip ? " bp-hs-flip" : "") + (active ? " bp-hs-active" : "")} style={{
    left: spot.x + "%",
    top: spot.y + "%"
  }}>
      <span className={"bp-hs-dot" + (isGood ? " bp-hs-dot-good" : "")} aria-hidden="true" />
      <span className="bp-hs-line" aria-hidden="true" style={spot.len ? {
    width: spot.len + "px"
  } : undefined} />
      <div className="bp-hs-anchor">
        <button type="button" className="bp-hs-chip" aria-expanded={active} aria-label={spot.label + ": " + spot.note} onMouseEnter={onActivate} onMouseLeave={onDismiss} onFocus={onActivate} onBlur={onDismiss} onClick={e => {
    e.stopPropagation();
    active ? onDismiss() : onActivate();
  }}>
          <span className={"bp-hs-mark" + (isGood ? " bp-hs-mark-good" : "")}>
            {isGood ? ICON_CHECK : ICON_CROSS}
          </span>
          {spot.short}
        </button>
        <div className="bp-hs-pop" role="tooltip">
          <span className={"bp-hs-pop-label" + (isGood ? " bp-hs-pop-label-good" : "")}>
            {spot.label}
          </span>
          <p>{spot.note}</p>
        </div>
      </div>
    </div>;
};

export const Media = ({src, caption, ratio = "phone", kind = "image", emphasize = false, showCaption = true, overlay = null}) => {
  const [open, setOpen] = useState(false);
  const [active, setActive] = useState(null);
  const [nat, setNat] = useState(null);
  const [vp, setVp] = useState({
    w: 1280,
    h: 800,
    dpr: 1
  });
  const dialogRef = useRef(null);
  const [canHover, setCanHover] = useState(true);
  const spots = kind === "image" ? spotsFor(src) : [];
  useEffect(() => {
    if (!window.matchMedia) return;
    const mq = window.matchMedia("(hover: hover) and (pointer: fine)");
    const read = () => setCanHover(mq.matches);
    read();
    mq.addEventListener ? mq.addEventListener("change", read) : mq.addListener(read);
    return () => {
      mq.removeEventListener ? mq.removeEventListener("change", read) : mq.removeListener(read);
    };
  }, []);
  useEffect(() => {
    const d = dialogRef.current;
    if (!d) return;
    if (open && !d.open) {
      if (d.showModal) d.showModal(); else d.setAttribute("open", "");
    } else if (!open && d.open) {
      if (d.close) d.close();
    }
  }, [open]);
  useEffect(() => {
    if (!open) return;
    const read = () => setVp({
      w: window.innerWidth,
      h: window.innerHeight,
      dpr: window.devicePixelRatio || 1
    });
    read();
    window.addEventListener("resize", read);
    const prev = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    return () => {
      window.removeEventListener("resize", read);
      document.body.style.overflow = prev;
    };
  }, [open]);
  const sized = (() => {
    if (!nat || !nat.w || !nat.h) return undefined;
    const r = nat.w / nat.h;
    const narrow = vp.w < 640;
    const availW = vp.w * (narrow ? 0.86 : 0.58);
    const availH = vp.h * (narrow ? 0.62 : 0.78);
    const vector = (/\.svg($|\?|#)/i).test(src);
    const maxScale = vector ? Infinity : Math.max(1, Math.min(1.5, vp.dpr));
    const w = Math.max(1, Math.round(Math.min(nat.w * maxScale, availW, availH * r)));
    return {
      width: w + "px",
      height: Math.round(w / r) + "px"
    };
  })();
  if (kind === "video") {
    return <figure className="bp-media">
        <div className={"bp-frame bp-frame-" + ratio + " bp-frame-auto"}>
          <video src={src} controls playsInline muted preload="metadata" controlsList="nofullscreen" aria-label={caption} />
        </div>
        {showCaption ? <figcaption className="bp-caption">{caption}</figcaption> : null}
      </figure>;
  }
  return <figure className="bp-media">
      <button type="button" className={"bp-frame bp-frame-" + ratio + (emphasize ? " bp-frame-emphasize" : "")} onClick={() => {
    setOpen(true);
    setActive(null);
  }} aria-label={spots.length ? "Open annotated view: " + caption + " (" + spots.length + " notes)" : "Enlarge: " + caption}>
        <img src={src} alt={caption} loading="lazy" />
        {overlay}
        <span className="bp-frame-hint" aria-hidden="true">
          {spots.length ? <>
              <span className="bp-frame-hint-n">{spots.length}</span>
              what to look at
            </> : "Enlarge"}
        </span>
      </button>

      {showCaption ? <figcaption className="bp-caption">{caption}</figcaption> : null}

      <dialog ref={dialogRef} className="bp-lightbox" aria-label={caption} onClose={() => setOpen(false)} onCancel={() => setOpen(false)} onClick={e => {
    if (e.target === dialogRef.current) setOpen(false);
  }}>
        {open ? <div className="bp-lb-stage" onClick={e => e.stopPropagation()}>
            <button type="button" className="bp-lb-close" onClick={() => setOpen(false)} aria-label="Close image" autoFocus>
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" aria-hidden="true"><path d="M18 6L6 18M6 6l12 12" /></svg>
            </button>
            <div className="bp-lb-frame" style={sized}>
              <img src={src} alt={caption} onLoad={e => setNat({
    w: e.currentTarget.naturalWidth,
    h: e.currentTarget.naturalHeight
  })} />
              {spots.map((spot, i) => <Marker key={i} spot={spot} n={i + 1} active={active === i} canHover={canHover} onActivate={() => setActive(i)} onDismiss={() => setActive(c => c === i ? null : c)} />)}
            </div>

            {!canHover && spots.length ? <ol className="bp-lb-notes">
                {spots.map((spot, i) => <li key={i} className={active === i ? "bp-lb-note bp-lb-note-active" : "bp-lb-note"}>
                    <button type="button" onClick={() => setActive(active === i ? null : i)}>
                      <span className={"bp-lb-note-n" + (spot.type === "good" ? " bp-lb-note-n-good" : "")}>
                        {i + 1}
                      </span>
                      <span>
                        <strong>{spot.label}</strong>
                        <span className="bp-lb-note-text">{spot.note}</span>
                      </span>
                    </button>
                  </li>)}
              </ol> : null}
            <p className="bp-lb-help">
              {spots.length ? canHover ? "Hover or tap a label to read why it matters. Esc to close." : "Tap a number to jump to its note." : caption}
            </p>
          </div> : null}
      </dialog>
    </figure>;
};

export const PointerArrow = ({left, top, angle = 0, length = 110, label}) => <span className="bp-arrow" aria-hidden="true" style={{
  left: left + "%",
  top: top + "%",
  width: length + "px",
  transform: "translate(-100%, -50%) rotate(" + angle + "deg)"
}}>
    <svg viewBox={"0 0 " + length + " 26"} width={length} height="26" focusable="false">
      <g fill="none" stroke="currentColor" strokeWidth="5" strokeLinecap="round" strokeLinejoin="round">
        <path d={"M4 13 H " + (length - 6)} />
        <path d={"M " + (length - 20) + " 4 L " + (length - 5) + " 13 L " + (length - 20) + " 22"} />
      </g>
    </svg>
    {label ? <span className="bp-sr">{label}</span> : null}
  </span>;

export const Panel = ({variant, points, media, ratio, kind}) => {
  const isDo = variant === "do";
  const list = Array.isArray(media) ? media : [media];
  return <div className={"bp-panel" + (isDo ? " bp-panel-do" : "")}>
      <span className={"bp-badge" + (isDo ? " bp-badge-do" : "")}>
        <span className={"bp-mark" + (isDo ? " bp-mark-do" : "")}>
          {isDo ? ICON_CHECK : ICON_CROSS}
        </span>
        {isDo ? "Do" : "Don't"}
      </span>

      {list.length > 1 ? <div className="bp-media-grid">
          {list.map((m, i) => <Media key={i} src={m.src} caption={m.caption} ratio={ratio} kind={kind} emphasize={m.emphasize} showCaption={false} />)}
        </div> : <Media src={list[0].src} caption={list[0].caption} ratio={ratio} kind={kind} />}

      <ul className="bp-points">
        {points.map((p, i) => <li className="bp-point" key={i}>
            <span className={"bp-mark" + (isDo ? " bp-mark-do" : "")}>
              {isDo ? ICON_CHECK : ICON_CROSS}
            </span>
            <span>{p}</span>
          </li>)}
      </ul>
    </div>;
};

export const DoDont = ({doPoints, dontPoints, doMedia, dontMedia, ratio = "phone", kind = "image"}) => <div className="bp-dodont">
    <Panel variant="do" points={doPoints} media={doMedia} ratio={ratio} kind={kind} />
    <Panel variant="dont" points={dontPoints} media={dontMedia} ratio={ratio} kind={kind} />
  </div>;

export const ChapteredVideo = ({src, chapters, ariaLabel}) => {
  const ref = useRef(null);
  const [active, setActive] = useState(0);
  const onTime = () => {
    const t = ref.current ? ref.current.currentTime : 0;
    const i = chapters.findIndex(c => t >= c.start && t < c.end);
    if (i !== -1 && i !== active) setActive(i);
  };
  const seek = i => {
    setActive(i);
    if (ref.current) {
      ref.current.currentTime = chapters[i].start + 0.05;
      const play = ref.current.play();
      if (play && play.catch) play.catch(() => {});
    }
  };
  return <div className="bp-chaptered">
      <video ref={ref} src={src} controls playsInline muted preload="metadata" onTimeUpdate={onTime} aria-label={ariaLabel} />
      <ol className="bp-chapters">
        {chapters.map((c, i) => <li key={i}>
            <button type="button" className="bp-chapter" aria-current={i === active} onClick={() => seek(i)}>
              <span className="bp-chapter-head">
                <span className="bp-chapter-n">{i + 1}</span>
                <span className="bp-chapter-title">{c.title}</span>
              </span>
              <span className="bp-chapter-note">{c.note}</span>
            </button>
          </li>)}
      </ol>
    </div>;
};

export const PromptCard = ({tag, title, copy, buttonLabel, prompt}) => {
  const [copied, setCopied] = useState(false);
  const [shown, setShown] = useState(false);
  const doCopy = () => {
    const done = () => {
      setCopied(true);
      setTimeout(() => setCopied(false), 2400);
    };
    const legacy = () => {
      try {
        const ta = document.createElement("textarea");
        ta.value = prompt;
        ta.setAttribute("readonly", "");
        ta.style.position = "fixed";
        ta.style.top = "-1000px";
        document.body.appendChild(ta);
        ta.select();
        const ok = document.execCommand("copy");
        document.body.removeChild(ta);
        if (ok) {
          done();
          return true;
        }
      } catch (ignored) {}
      return false;
    };
    if (navigator.clipboard && navigator.clipboard.writeText) {
      navigator.clipboard.writeText(prompt).then(done, () => {
        if (!legacy()) setShown(true);
      });
    } else if (!legacy()) {
      setShown(true);
    }
  };
  return <div className="bp-prompt">
      <span className="bp-num">{tag}</span>
      <h3>{title}</h3>
      <p>{copy}</p>
      <div className="bp-prompt-actions">
        <button type="button" className="al-btn al-btn-primary" onClick={doCopy}>
          {copied ? "Copied" : buttonLabel}
        </button>
        <button type="button" className="al-btn al-btn-secondary" onClick={() => setShown(!shown)}>
          {shown ? "Hide prompt" : "Read the prompt"}
        </button>
      </div>
      {shown ? <pre className="bp-prompt-pre">{prompt}</pre> : null}
    </div>;
};

export const CORE_STAGES = [{
  group: "Pop-up placement",
  items: ["Surfaces within the first 2-5 minutes of play time", "Force-shown, no tap required", "Re-shown at least every 30 minutes to unlinked players", "Never fired straight after a rewarded ad", "Not capped to once per session", "Rolled out to at least 90% of users"]
}, {
  group: "Pop-up design",
  items: ["Covers at least half the screen and native to your game's art and fonts", "Dismissal blocked for a few seconds", "$10 sign-up bonus stated outright", "In-game currency reward shown on the banner", "One clear CTA focused on rewards, no \"Sign Up\" wording", "Freecash icon used", "Includes a social-proof or trust-signal line"]
}, {
  group: "Homescreen icon",
  items: ["Freecash icon carried on the button", "Reward signal (cash, coins or gift) visible on it", "Draws the eye: notification bubble, animation or colour highlight", "Placed in the homescreen corner or in the shop", "Stays on screen for anyone who hasn't linked", "Stays visible after linking, as the route into Rewarded Progression and the Earn Tab"]
}, {
  group: "User flow (SDK 1.1+)",
  items: ["In-game reward paid out on the player's next return", "Player is shown that they received the reward", "Player is nudged toward the Freecash icon once, not auto-opened", "Rewarded Progression walked through once", "Icon badged with a red \"1\" on every milestone via reward notification"]
}];

export const BONUS_STAGE = {
  group: "Earn Tab",
  items: ["Own dedicated pop-up for the Earn Tab", "Exact offer reward and payout stated on that pop-up", "One clear CTA focused on Rewards, no \"Sign Up\" wording", "One-time tutorial: what an offer is and what it pays", "Earn Tab reachable from the same Freecash homescreen icon", "Earn Tab survey submitted so we can set it up"]
};

export const CORE_TOTAL = CORE_STAGES.reduce((n, g) => n + g.items.length, 0);
export const CORE_COINS = CORE_TOTAL * 10;
export const BONUS_COINS = BONUS_STAGE.items.length * 15;
export const COIN_SRC = "/images/best-practices/link-coin.png";

export const CheckRow = ({label, checked, onToggle}) => <li className="bp-check">
    <label>
      <input type="checkbox" checked={checked} onChange={e => onToggle(e.target.checked)} />
      <span className="bp-check-box" aria-hidden="true">{ICON_CHECK}</span>
      <span className="bp-check-label">{label}</span>
    </label>
  </li>;

export const Progress = ({pct}) => <div className="bp-progress">
    <span className="bp-progress-track" aria-hidden="true">
      <span className="bp-progress-fill" style={{
  "--bp-pct": pct
}} />
    </span>
    <span className="bp-progress-pct">{pct}%</span>
  </div>;

export const Checklist = () => {
  const [checked, setChecked] = useState({});
  const [unlocked, setUnlocked] = useState(0);
  const [bonusOpen, setBonusOpen] = useState(false);
  const toggle = item => next => setChecked(prev => ({
    ...prev,
    [item]: next
  }));
  const doneIn = items => items.filter(i => checked[i]).length;
  const coreDone = CORE_STAGES.reduce((n, g) => n + doneIn(g.items), 0);
  const bonusDone = doneIn(BONUS_STAGE.items);
  const coins = coreDone * 10 + bonusDone * 15;
  const allDone = coreDone === CORE_TOTAL;
  useEffect(() => {
    const cur = CORE_STAGES[unlocked];
    if (cur && cur.items.every(i => checked[i]) && unlocked < CORE_STAGES.length - 1) {
      setUnlocked(unlocked + 1);
    }
  }, [checked, unlocked]);
  return <div>
      <div className="bp-coinbar">
        <span className="bp-coinpill">
          <img src={COIN_SRC} alt="" aria-hidden="true" />
          <span aria-live="polite">{coins} / {CORE_COINS + BONUS_COINS} Link Coins</span>
        </span>
        <span className="bp-coinbar-note">{CORE_COINS} core + {BONUS_COINS} bonus</span>
      </div>

      {CORE_STAGES.map((stage, gi) => {
    const done = doneIn(stage.items);
    const complete = done === stage.items.length;
    const pct = Math.round(done / stage.items.length * 100);
    if (gi > unlocked) {
      return <div className="bp-stage bp-stage-locked" key={stage.group}>
              <span className="bp-stage-n" aria-hidden="true">
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" style={{
        width: "0.85rem",
        height: "0.85rem"
      }}>
                  <rect x="4" y="10" width="16" height="10" rx="2" />
                  <path d="M8 10V7a4 4 0 0 1 8 0v3" />
                </svg>
              </span>
              <span>
                <strong>Stage {gi + 1} locked</strong>
                <span className="bp-chapter-note">
                  {stage.items.length} checks, worth {stage.items.length * 10} Link Coins.
                </span>
              </span>
            </div>;
    }
    const next = CORE_STAGES[gi + 1];
    const canUnlock = gi === unlocked && !!next && done >= Math.ceil(stage.items.length / 2);
    return <div className={"bp-stage" + (complete ? " bp-stage-complete" : "")} key={stage.group}>
            <div className="bp-stage-head">
              <span className="bp-stage-head-l">
                <span className="bp-stage-n" aria-hidden="true">{gi + 1}</span>
                <h3>{stage.group}</h3>
              </span>
              <span className="bp-stage-score">
                <img src={COIN_SRC} alt="" aria-hidden="true" />
                {done * 10} / {stage.items.length * 10}
              </span>
            </div>

            <Progress pct={pct} />

            <ul className="bp-checks">
              {stage.items.map(item => <CheckRow key={item} label={item} checked={!!checked[item]} onToggle={toggle(item)} />)}
            </ul>

            {gi === unlocked && next ? <div className="bp-unlock">
                <button type="button" className="al-btn al-btn-primary" disabled={!canUnlock} onClick={() => setUnlocked(gi + 1)}>
                  {canUnlock ? "Unlock stage " + (gi + 2) : "Tick " + (Math.ceil(stage.items.length / 2) - done) + " more to unlock"}
                </button>
                {canUnlock && !complete ? <span className="bp-unlock-note">
                    {stage.items.length - done} boxes still open in this stage.
                  </span> : null}
              </div> : null}
          </div>;
  })}

      {bonusOpen ? <div className={"bp-stage bp-stage-bonus" + (bonusDone === BONUS_STAGE.items.length ? " bp-stage-complete" : "")}>
          <div className="bp-stage-head">
            <span className="bp-stage-head-l">
              <span className="bp-num">Bonus stage</span>
              <h3>{BONUS_STAGE.group}</h3>
            </span>
            <span className="bp-stage-score">
              <img src={COIN_SRC} alt="" aria-hidden="true" />
              {bonusDone * 15} / {BONUS_COINS}
            </span>
          </div>
          <p className="bp-sub-intro">
            Always open, never required. Every box here is worth 15 Link Coins instead of 10, as the
            Earn Tab is an additional feature inside of Link.
          </p>
          <Progress pct={Math.round(bonusDone / BONUS_STAGE.items.length * 100)} />
          <ul className="bp-checks">
            {BONUS_STAGE.items.map(item => <CheckRow key={item} label={item} checked={!!checked[item]} onToggle={toggle(item)} />)}
          </ul>
        </div> : <div className="bp-bonus-invite">
          <p>Setting up the Earn Tab? Bonus stage: {BONUS_COINS} extra Link Coins.</p>
          <button type="button" className="al-btn al-btn-primary" onClick={() => setBonusOpen(true)}>
            Unlock bonus stage
          </button>
        </div>}

      <p className="bp-verdict">
        {allDone ? "Full board, all " + CORE_COINS + " core Link Coins. This is the strongest Link setup we can advise for today. A/B test from here, and keep us updated about the results." : "This is the same checklist we use to audit live integrations. Every unticked box is a known drop in install-to-link rate or LTV, so the target is " + CORE_COINS + " core Link Coins plus the " + BONUS_COINS + " bonus coins on the Earn Tab."}
      </p>
    </div>;
};

export const MONEY_RULE = `
## Hard rule on money wording

There is exactly ONE dollar figure allowed anywhere on the pop-up or entry point: the **$10 sign-up bonus**. Nothing else may carry a dollar amount.

- Never recommend adding a second money number, for example "$5 in Gift Cards", "Earn $50 a month", "$2 cash back" or a payout total.
- Vague reward wording like "Real Rewards" is fixed by naming the reward type and its exact in-game amount ("500 Coins", "3 Gems"), not by attaching a dollar value.
- Payout options (cash, gift cards, PayPal, Visa, Amazon) appear as logos or plain labels with NO amounts attached.
- If the material shows more than one dollar figure, mark it a fail and instruct the team to remove every dollar amount except the $10 sign-up bonus.
`;

export const SHARED_TAIL = `
## Rules for you, the assistant

- Never start the audit before you have the visual material. If it is missing, ask for it and stop.
- Never guess. If something cannot be judged from the screenshot or the answers, mark it "Cannot tell" and say exactly what you would need to see.
- Do not invent rules beyond the ones listed above. These rules come from Almedia's published Link best practices.
- Never propose a second dollar figure. The $10 sign-up bonus is the only monetary number allowed.
- Write for a design or product manager: plain language, no code, no SDK details.

## Output format

1. **Scorecard**: a table with three columns, Rule | Pass / Fail / Cannot tell | What you can see.
2. **Fix list**: every failed rule as a concrete change, ranked by impact on install-to-link rate and LTV. One sentence each, written as an instruction ("Move the pop-up to fire at minute 3 of the first session").
3. **Copy check**: quote the exact wording currently used on the CTA and the reward lines, and give the replacement wording where it breaks a rule.
4. **One-line verdict**: how far this integration is from the strongest setup, and the single highest-impact change.
5. Close with: full guide at https://dev.almedia.co/publisher/best-practices and "Send your setup to link@almedia.co for a human review."
`;

export const POPUP_PROMPT = `You are auditing the Freecash Link pop-up inside a mobile game against Almedia's Link best practices. Your job is to tell the team exactly what is wrong with their pop-up and how to fix it.

## Step 1: get the visual

If no screenshot or screen recording of the pop-up is attached to this message, ask for it now and wait. Say exactly what you need:
- A full screen capture of the pop-up as a player sees it, gameplay behind it included.
- Ideally also a short screen recording showing the moment it appears.

Do not audit anything until you have it.

## Step 2: ask what a picture cannot answer

Once you have the visual, ask these questions in one short numbered batch and wait for the answers:

1. How many minutes into the player's very first session does the pop-up first appear?
2. Does it appear on its own, or does the player have to tap an icon, menu or shop tab?
3. If the player dismisses it, how often is it shown again? (Every X minutes of play time? Only once per session? Once per install?)
4. Can it fire directly after a rewarded ad or an interstitial?
5. What percentage of users currently see it?
6. Is dismissal blocked for the first few seconds, and is a countdown shown?
7. When a player links, is the in-game currency reward shown on the pop-up actually granted, and how soon?
8. Is the pop-up built in your game's own fonts, colours and UI frames, or is it Freecash-branded artwork?

## Step 3: check against these rules

**Timing and placement**
- Appears within the first 2-5 minutes of play time, inside the very first session.
- Force-shown: it appears on its own, no tap required.
- Re-shown at least every 30 minutes of play time to any player who has not linked yet.
- Rolled out to at least 90% of users.
- Never fired straight after a rewarded ad or an interstitial.
- Not capped to "once per session" or "once per install".
- The surrounding screen is quiet: the pop-up is clearly the only thing on screen.

**Design**
- Covers at least half the screen, with the gameplay behind it dimmed out. A small card over barely dimmed gameplay fails.
- Built game native: the game's own fonts, colours, button shapes and art style. Only the Freecash mark and the exact reward values stay as-is.
- Dismissal blocked for the first 5 seconds, shown as a small countdown next to a greyed-out close button.
- The $10 sign-up bonus is stated outright in plain text. This is the single most impactful element.
- The in-game currency reward for linking (coins, gems, tickets) is shown on the banner, and actually granted on linking.
- Real-world payout options are shown as logos or plain labels (cash, gift cards, PayPal / Visa / Amazon) with no dollar amounts attached, so the value reads as real money.
- Exactly one reward-led CTA. Shippable wording: "Earn Rewards", "Start earning rewards", "Link Account & Get Rewards".
- No "Sign Up", "Link & SignUp" or "Link to Freecash" wording. Those describe an action, not a benefit.
- No second competing button such as "Check Link" next to the main CTA.
- The Freecash icon is present so the offer is recognisable and trusted.
- One social-proof or trust-signal line is present. Shippable wording: "Join millions of players already earning daily." or "Join a community of over 80 million players earning real rewards."
- Uncluttered: a balance of icons and short text, never a wall of copy.
- No vague rewards ("Coins for playing", "Cash bonus", "Real Rewards"). Every reward is named with its exact in-game amount, for example "500 Coins".
- Only one dollar figure on the whole pop-up: the $10 sign-up bonus.
` + MONEY_RULE + SHARED_TAIL;

export const ENTRY_PROMPT = `You are auditing the persistent Freecash Link entry point (the homescreen icon) inside a mobile game against Almedia's Link best practices. Your job is to tell the team exactly what is wrong with their entry point and how to fix it.

## Step 1: get the visual

If no screenshot is attached to this message, ask for it now and wait. Say exactly what you need:
- A full screenshot of the game homescreen with the entry point visible in place, not the icon file on its own.
- A close-up of the icon itself.
- If the entry point also lives in the shop or as a banner, a screenshot of that too.

Do not audit anything until you have it.

## Step 2: ask what a picture cannot answer

Once you have the visual, ask these questions in one short numbered batch and wait for the answers:

1. Where exactly does the entry point sit? (Homescreen corner, shop, banner, menu, somewhere else?)
2. Is it visible permanently to every player who has not linked yet, or only during certain sessions or after a level gate?
3. Does it stay visible after the player has linked?
4. After linking, what does tapping it open: Rewarded Progression, the Earn Tab, or the Freecash app?
5. Does it carry an attention cue: a red notification badge, an animation, or a colour highlight that separates it from ordinary shop buttons?
6. Do you fire a reward notification badge when a milestone is reached?
7. Does the player ever have to open a menu, settings screen or sub-page to reach it?
8. Is the frame around it built in your game's own art style?

## Step 3: check against these rules

**The icon itself**
- A persistent, tappable icon that carries the Freecash mark so it is instantly identifiable.
- The surrounding frame is styled natively to the game's theme, while the Freecash mark stays intact.
- A reward signal is on it: cash, coins or a gift cue, so the player knows a reward sits behind it.
- It draws the eye: a red notification badge on the corner, a subtle animation, or a colour highlight.
- It has a label. A bare Freecash logo or the plain Freecash app icon used alone reads as a random brand icon and fails.
- It does not look like just another shop button.

**Placement and lifecycle**
- Permanently on screen for anyone who has not linked yet, not just for one session.
- Never buried in a settings menu, an overflow menu or a sub-page.
- Good placements: the homescreen corner first, or inside the shop next to the currency packs, or a wide in-game reward banner carrying the reward value in text.
- It stays visible after linking as well. This is load bearing: it is the route into Rewarded Progression and the Earn Tab, so hiding it after linking cuts off both the LTV feature and the revenue feature.
- Badged with a red "1" on every milestone the player achieves, via a reward notification.
- Any reward cue on the icon or banner names an exact in-game amount, never a vague line like "Real Rewards". The only dollar figure allowed anywhere is the $10 sign-up bonus.

**Why it matters**
- Most players do not link on the first pop-up. The persistent icon carries the long tail of links, which is a large share of total linked users and therefore of the LTV lift.
` + MONEY_RULE + SHARED_TAIL;

<section className="bp-hero">
  <span className="bp-hero-blob bp-hero-blob-a" aria-hidden="true" />

  <span className="bp-hero-blob bp-hero-blob-b" aria-hidden="true" />

  <div className="bp-hero-inner">
    <img className="bp-hero-logo" src="https://mintcdn.com/almedia-c3396e23/LZgAT9yWU8046jbY/images/best-practices/link-logo-blue.png?fit=max&auto=format&n=LZgAT9yWU8046jbY&q=85&s=ba2b731c7eacd0d04301c61a4064b1be" alt="Almedia Link" width="472" height="151" data-path="images/best-practices/link-logo-blue.png" />

    <h1 className="bp-hero-title">Best Practices</h1>

    <p className="bp-hero-sub">
      These are the patterns behind a high install-to-link rate, and a clear lift in LTV for
      linked users. Follow them to see better results.
    </p>
  </div>
</section>

<div className="bp-readme">
  <span className="bp-readme-blob" aria-hidden="true" />

  <span className="bp-num">How to read this guide</span>
  <h2>This is a layout guide, you make it game native</h2>

  <p>
    Every example is illustrated with Freecash art to show the structure. Rebuild it{" "}
    <strong>game native</strong>: your fonts, colours, frames, art style. Keep only the Freecash
    logo at certain touch points, and keep the exact reward values intact.
  </p>
</div>

## Outcome 01 · How to maximize your install-to-link ratio

<p className="bp-outcome-intro">
  Three levers decide how many installs turn into linked accounts: when the pop-up fires, what it
  says, and whether players can still find the offer later.
</p>

### Pop-up design

<p className="bp-sub-intro">
  The design of the pop-up should be native to your game — font, style, frames. The example
  artwork only illustrates which components it should include.
</p>

<DoDont
  ratio="phone"
  doMedia={{ src: "/images/best-practices/popup-do-tight.webp", caption: "Freecash pop-up covering about three quarters of the screen over dimmed gameplay, with a 5 second countdown next to the greyed-out close button, the in-game coin reward, cash and gift cards, a $10 sign-up bonus, one 'Start earning rewards' CTA and a social-proof line." }}
  doPoints={[
"Covers at least half the screen, gameplay dimmed behind it",
"Dismissal blocked for the first 5 seconds",
"$10 bonus stated outright",
"In-game currency reward on the banner",
"One clear CTA focused on Rewards",
"Freecash icon used",
]}
  dontMedia={{ src: "/images/best-practices/popup-dont.png", caption: "Small pop-up card floating over barely dimmed gameplay with vague 'Coins for playing' and 'Cash bonus' rows, a 'Sign Up' button and a competing 'Check Link' button, and no social proof." }}
  dontPoints={[
"Small card over barely dimmed gameplay",
"Close button available immediately",
"No $10 bonus stated anywhere",
"No in-game currency reward shown",
"Two competing CTAs, and \"Sign Up\" wording on the main one",
"No Freecash icon, so the offer looks unknown",
]}
/>

<div className="bp-copies">
  <h4>Copy you can ship as-is</h4>

  <div className="bp-copies-grid">
    <div>
      <h4>Social proof</h4>

      <ul>
        <li>“Join millions of players already earning daily.”</li>
        <li>“Join a community of over 80 million players earning real rewards.”</li>
      </ul>
    </div>

    <div>
      <h4>CTA</h4>

      <ul>
        <li>“Earn Rewards”</li>
        <li>“Start earning rewards”</li>
        <li>“Link Account & Get Rewards”</li>
      </ul>
    </div>
  </div>
</div>

<div className="bp-tip">
  <span className="bp-num">Bonus tip</span>

  <p>
    Make the in-game reward escalating: raise it on every re-show after a dismissal, up to one
    final best offer.
  </p>
</div>

### Where to place the pop-up

<p className="bp-sub-intro">
  Timing alone separates the strongest integrations from the average ones. The same creative
  performs very differently depending on when it fires.
</p>

<DoDont
  ratio="phone"
  kind="video"
  doMedia={{ src: "/images/best-practices/popup-timing-do-trimmed.mp4", caption: "The pop-up surfaces on its own a few minutes into a live session, with no ad before it and no tap from the player." }}
  doPoints={[
"Surfaces within the first 2-5 minutes play time",
"Force-shown, no tap required",
"Re-shown at least every 30 minutes to unlinked players",
"Attention is still fresh, so install-to-link peaks",
]}
  dontMedia={{ src: "/images/best-practices/popup-timing-dont.mp4", caption: "A rewarded ad finishes and the pop-up fires the instant it closes — two interruptions back to back." }}
  dontPoints={[
"Waits until the player is deep in the session, or fires straight after a rewarded ad",
"Waits for the player to tap something",
"Capped to \"once per session\", never re-shown",
"Player is already fatigued, most never see it twice",
]}
/>

<PromptCard tag="AI audit" title="Have an AI check your pop-up design" copy="Copy the prompt, paste it into ChatGPT, Claude or Gemini with a screenshot of your pop-up. It asks for what it needs, then scores your pop-up against every rule in this guide." buttonLabel="Copy pop-up prompt" prompt={POPUP_PROMPT} />

### Homescreen icon

<p className="bp-sub-intro">
  Most players don't link on the first pop-up. This is what brings them back without interrupting
  them again — and it is the most commonly missed part of an integration.
</p>

<DoDont
  ratio="icon"
  doMedia={[
{ src: "/images/best-practices/icon-rewards-fab-v2.webp", caption: "Homescreen icon with the Freecash mark surrounded by coins, a gift and gems, labelled Freecash Rewards." },
{ src: "/images/best-practices/icon-rewards-b.png", caption: "Homescreen icon showing coins and cash reward art with a FreeCash Rewards label.", emphasize: true },
]}
  doPoints={[
"Carries the Freecash icon",
"Reward signal on it: cash, coins or gift",
"Draws the eye: notification bubble, animation or colour highlight",
"Keep it on screen for anyone who hasn't linked",
"Good placements: homescreen corner, or in the shop",
]}
  dontMedia={[
{ src: "/images/best-practices/icon-dont-a.svg", caption: "Bare green Freecash logo mark used alone as the entry point, with no reward art and no label." },
{ src: "/images/best-practices/icon-dont-b.svg", caption: "Plain Freecash app logo used as the entry point, with no reward cue and no label." },
]}
  dontPoints={[
"Bare app logo used alone, no Freecash reward context",
"No reward signal on it: no cash, no coins, no gift",
"No highlighting: no notification badge, no animation, no colour pop",
"Buried in a settings menu, so unlinked players never pass it",
"No label, so it reads as another shop button and gets ignored",
]}
/>

### Where to place the entry point

<p className="bp-sub-intro">
  Three placements work. Ship at least one, and the homescreen corner first.
</p>

<div className="bp-placements bp-placements-2">
  <div className="bp-place">
    <span className="bp-num">Homescreen</span>

    <Media
      src="/images/best-practices/placement-homescreen.webp"
      ratio="phone"
      overlay={<PointerArrow left={31} top={25.5} angle={180} length={96}
             label="Arrow pointing at the FreeCash Rewards icon in the homescreen corner" />}
      caption="Game homescreen with the FreeCash Rewards icon pinned in the corner of the HUD, showing coins and cash art with a label under it."
    />

    <h4>Homescreen corner</h4>

    <p>
      Always on the main screen: every unlinked player passes it every session, and every linked
      player uses it to reach Rewarded Progression or the Earn Tab.
    </p>
  </div>

  <div className="bp-place">
    <span className="bp-num">Banner</span>

    <Media
      src="/images/best-practices/placement-banner-rounded.png"
      ratio="phone"
      overlay={<PointerArrow left={50} top={93.5} angle={90} length={52}
             label="Arrow pointing at the Freecash offer banner at the bottom of the screen" />}
      caption="Game homescreen with a Freecash special offer banner docked below the bottom nav, stating 300 coins, a $10 bonus and an Earn Rewards button."
    />

    <h4>In-game banner</h4>
    <p>A wide strip docked against the bottom nav, carrying the reward value in text.</p>
  </div>
</div>

<div className="bp-placements">
  <div className="bp-place">
    <span className="bp-num">Shop</span>

    <Media
      src="/images/best-practices/placement-shop.png"
      ratio="shop"
      overlay={<PointerArrow left={66} top={56} angle={0} length={90}
             label="Arrow pointing at the FreeCash Rewards tile beside the coin packs" />}
      caption="In-game coin shop screen with the FreeCash Rewards tile placed as a third option next to the coin packs."
    />

    <h4>Inside the shop</h4>
    <p>Sits next to the currency packs, exactly where players already come to get more.</p>
  </div>
</div>

<PromptCard tag="AI audit" title="Have an AI check your entry point" copy="Copy the prompt, paste it into ChatGPT, Claude or Gemini with a screenshot of your homescreen. It asks the questions a screenshot can't answer, then scores your entry point against this guide." buttonLabel="Copy entry point prompt" prompt={ENTRY_PROMPT} />

## Outcome 02 · How to maximize LTV with Rewarded Progression

<p className="bp-outcome-intro">
  <span className="bp-pill">SDK 1.1+ needed</span>
</p>

<p className="bp-outcome-intro">
  Rewarded Progression lives inside your game. The player sees their progress and their rewards
  without ever opening Freecash, which is what keeps them playing and cuts churn.
</p>

### User flow after linking

<p className="bp-sub-intro">Pay out loud, then show them where the progression lives.</p>

<ChapteredVideo
  src="/images/best-practices/rewarded-progression.mp4"
  ariaLabel="Screen recording of the post-link flow"
  chapters={[
{ start: 0, end: 4.4, title: "Link the account", note: "The player signs up and links in a few taps." },
{ start: 4.4, end: 10.5, title: "Pay the reward out loud", note: "The player lands back in the game and immediately sees the in-game currency they earned. This is the moment that proves the player's linking worked." },
{ start: 10.5, end: 21.5, title: "Show the entry point via a short tutorial", note: "A short tutorial points at the Freecash icon in the HUD. A permanent, reward-signalling entry point is what turns one payout into repeat earning sessions." },
{ start: 21.5, end: 34.8, title: "End inside Rewarded Progression", note: "The tutorial ends inside Rewarded Progression, so the player sees what the next milestone is worth and gets a concrete reason to come back." },
]}
/>

### Badge every milestone

<p className="bp-sub-intro">A reward notification is what pulls the player back into progression.</p>

<div className="bp-tip">
  <span className="bp-num">Bonus tip</span>

  <p>
    Badge the icon with a red “1” on every milestone the user achieves, using a reward
    notification. <a href="https://github.com/almedia-tm/almedia-link-sdk/blob/main/Documentation~/integration-guide.md#reward-notifications">Technical guide for this</a>
  </p>
</div>

## Outcome 03 · How to maximize revenue with the Earn Tab

<p className="bp-outcome-intro">
  <span className="bp-pill">SDK 1.1+ needed</span>
</p>

<p className="bp-outcome-intro">
  The Earn Tab pays you a \$3 CPA for every completed offer. It is an additional feature for
  already-linked users.
</p>

### User flow for the Earn Tab

<p className="bp-sub-intro">
  Players will not find the Earn Tab on their own, so it gets its own launch moment: a dedicated
  pop-up, one short tutorial, then the same Freecash homescreen icon as the permanent way back.
</p>

<ChapteredVideo
  src="/images/best-practices/earn-tab-flow.mp4"
  ariaLabel="Screen recording of the Earn Tab flow, from the dedicated pop-up through the Earn Tab offers and back into the game"
  chapters={[
{ start: 0, end: 4.2, title: "Fire a dedicated Earn Tab pop-up", note: "The player is in the game when the Earn Tab gets its own pop-up moment. Do not put it onto the linking pop-up. It needs its own dedicated pop-up, to increase conversion and to explain the function to users." },
{ start: 4.2, end: 13, title: "Land in the Earn Tab", note: "The Earn Tab opens inside your game, featuring offers from other games. Every completed offer is a $3 CPA on your side." },
{ start: 13, end: 15.5, title: "Back to the game, icon stays", note: "The player closes the Earn Tab and returns to gameplay with the Freecash icon still on the HUD. That icon is the permanent way back in." },
{ start: 15.5, end: 19.8, title: "One icon, two tabs", note: "The same icon opens Rewarded Progression and the Earn Tab side by side, so players who came for progression discover the offers too." },
]}
/>

### Earn Tab pop-up

<p className="bp-sub-intro">
  Same rules as the link pop-up: exact reward stated, one CTA, no wall of text.
</p>

<DoDont
  ratio="phone"
  doMedia={{ src: "/images/best-practices/earn-popup-do.webp", caption: "Earn Tab pop-up covering most of the screen with the Freecash mark, three numbered steps, a milestone progress bar, an exact gem reward and one 'Earn Rewards' CTA." }}
  doPoints={[
"Own dedicated pop-up for the Earn Tab",
"State the exact offer reward and the payout up front",
"Explain the milestone: what to reach and how short it is",
"One clear CTA focused on Rewards, no sign-up wording, Freecash mark present",
]}
  dontMedia={{ src: "/images/best-practices/earn-popup-dont.png", caption: "Small Earn Tab pop-up floating over live gameplay with no Freecash mark, no reward value, vague steps and a 'Sign Up Now' button." }}
  dontPoints={[
"Small card bolted over undimmed gameplay, no dedicated moment",
"No exact reward stated anywhere, the player has to guess",
"Vague milestone copy like \"keep playing, see what happens\"",
"\"Sign Up Now\" wording and no Freecash mark, although the player is already linked",
]}
/>

<div className="bp-tip">
  <span className="bp-num">Launching the Earn Tab</span>

  <p>
    Fill in the short Earn Tab survey so we have every asset we need to set it up for you.
  </p>

  <p>
    <a className="al-btn al-btn-primary" href="https://link-asset-hub.lovable.app/earn-tab-survey">
      Start the Earn Tab survey
    </a>
  </p>
</div>

## Full user flow from start to end

<p className="bp-outcome-intro">
  The whole journey in one recording: the pop-up firing in the first session, the link and Freecash
  sign-up, back into the game for the in-game payout, the homescreen icon, and finally Rewarded
  Progression.
</p>

<div className="bp-fullflow">
  <Media src="/images/best-practices/full-flow.mp4" kind="video" ratio="phone" caption="The complete flow end to end, from the first session and the pop-up firing, through linking and the Freecash sign-up, back into the game for the in-game payout, the homescreen icon, and finally Rewarded Progression." />
</div>

## Checklist

<p className="bp-outcome-intro">
  Every box ticked is the strongest setup we know of today for install-to-link rate and LTV per
  linked player. Each core box is worth 10 Link Coins; the Earn Tab is a bonus stage worth 15 a box.
</p>

<Checklist />

## Submit your integration

<div className="bp-close">
  <span className="bp-close-blob" aria-hidden="true" />

  <p className="bp-close-title">Submit your integration. We will give you feedback.</p>

  <p>
    Send us your Link setup and we will review it against everything on this page. Running your own
    A/B tests? Send those results too.
  </p>

  <p>
    <a className="bp-btn-onblue" href="https://link-asset-hub.lovable.app/survey">
      Submit your integration
    </a>
  </p>

  <p>Questions: [link@almedia.co](mailto:link@almedia.co)</p>
</div>
