Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/assets/Inline/_gallery.html

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions src/assets/Inline/copy.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/Inline/library.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/Inline/retry.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/Inline/send.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/Inline/sparkle.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/Inline/stop.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/Inline/thumbs-down.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/Inline/thumbs-up.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file added src/assets/shared/video/ai-editor-refine.mp4
Binary file not shown.
Binary file added src/assets/shared/video/ai-editor-restyle.mp4
Binary file not shown.
Binary file not shown.
259 changes: 259 additions & 0 deletions src/components/AIPromo.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
---
/**
* Spotlight card pointing at an AI capability from a related article.
*
* Shaped like the discover cards on the docs home page — icon tile, heading,
* body, call to action — rather than like a callout, because it is an invitation
* to go somewhere rather than an aside about the current page.
*
* The whole card is the link, so the body must NOT contain a link of its own —
* nested anchors are invalid HTML. Write the body as plain prose and let `cta`
* carry the call to action.
*
* <AIPromo id="flow-ai-editor" title="Skip the Design tab entirely.">
* Tell the AI Editor to "give the paywall a dark, premium feel" ...
* </AIPromo>
*/
interface Props {
/**
* Doc id to link to. Defaults to the AI Editor, which is every current call
* site — pass it only when pointing at a different AI feature.
*/
id?: string;
/** Absolute URL, when the target is not a doc. Ignored if `id` is set. */
href?: string;
/** The bold lead line. Keep it to one short sentence. */
title: string;
/** Call-to-action text at the foot of the card. */
cta?: string;
className?: string;
}

const {
id = 'flow-ai-editor',
href,
title,
cta = 'Explore the AI Editor',
className,
} = Astro.props;

// Same resolution as Button.astro, so doc ids behave identically across components.
const baseUrl = import.meta.env.BASE_URL.replace(/\/+$/, '') || '/docs';
const finalHref = id ? `${baseUrl}/${id}` : href;
---

<a href={finalHref} class:list={['ai-promo', className]}>
<span class="ai-promo-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="currentColor">
<path
class="ai-promo-star ai-promo-star-lg"
d="M10.5 3.5l1.55 4.2 4.2 1.55-4.2 1.55-1.55 4.2-1.55-4.2-4.2-1.55 4.2-1.55 1.55-4.2z"
></path>
<path
class="ai-promo-star ai-promo-star-sm"
d="M17.5 14l.8 2.15 2.15.8-2.15.8-.8 2.15-.8-2.15-2.15-.8 2.15-.8.8-2.15z"
></path>
</svg>
</span>

{/* Divs, not spans: MDX wraps slot content in <p>, and a <p> inside a <span> is
invalid. <a> takes its content model from its parent, so flow content is fine here. */}
<div class="ai-promo-content">
{/* Deliberately not an <h3>: the table of contents scrapes `article h2, article h3`,
and a promo card has no business appearing in a page's navigation. */}
<div class="ai-promo-title">{title}</div>
<div class="ai-promo-desc"><slot /></div>
<div class="ai-promo-cta">
{cta}<span class="ai-promo-arrow" aria-hidden="true">&rsaquo;</span>
</div>
</div>
</a>

<style>
a.ai-promo {
position: relative;
display: flex;
align-items: flex-start;
gap: 1.25rem;
margin: 1.75rem 0;
padding: 1.375rem 1.5rem;
border: 1px solid #ddd6fe;
border-radius: 1rem;
background: linear-gradient(135deg, #faf8ff 0%, #f3eeff 100%);
text-decoration: none;
overflow: hidden;
transition:
border-color 0.3s,
box-shadow 0.3s,
transform 0.3s;
}

a.ai-promo:hover {
border-color: #c4b5fd;
box-shadow: 0 4px 12px rgb(124 58 237 / 0.1);
transform: translateY(-2px);
text-decoration: none;
}

a.ai-promo:focus-visible {
outline: 2px solid #6720ff;
outline-offset: 3px;
}

:global(.dark) a.ai-promo {
background: var(--bg-secondary, #0f0f10);
border-color: var(--border-primary, rgb(167 139 250 / 0.25));
}

:global(.dark) a.ai-promo:hover {
border-color: rgb(167 139 250 / 0.5);
box-shadow: 0 4px 12px rgb(0 0 0 / 0.3);
}

:global(.dark) a.ai-promo:focus-visible {
outline-color: #a78bfa;
}

.ai-promo-icon {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
border-radius: 0.75rem;
background: #ffffff;
color: #7c3aed;
box-shadow: 0 1px 2px rgb(103 32 255 / 0.08);
}

:global(.dark) .ai-promo-icon {
background: rgb(255 255 255 / 0.08);
color: #c4b5fd;
box-shadow: none;
}

.ai-promo-icon svg {
width: 26px;
height: 26px;
}

/* Two stars twinkling out of phase — a sparkle rather than a pulse. */
.ai-promo-star {
transform-box: fill-box;
transform-origin: center;
animation: ai-promo-twinkle 3.2s ease-in-out infinite;
}

.ai-promo-star-sm {
animation-delay: 0.7s;
}

@keyframes ai-promo-twinkle {
0%,
55%,
100% {
opacity: 1;
transform: scale(1);
}
25% {
opacity: 0.55;
transform: scale(0.82);
}
40% {
opacity: 1;
transform: scale(1.12);
}
}

.ai-promo-content {
flex: 1;
min-width: 0;
}

.ai-promo-title {
display: block;
font-size: 1.0625rem;
font-weight: 650;
line-height: 1.4;
color: var(--text-primary, #0f172a);
margin-bottom: 0.3rem;
}

.ai-promo-desc {
display: block;
font-size: 0.9375rem;
line-height: 1.6;
color: var(--text-secondary, #475569);
}

/* Body arrives as MDX, so it may carry a paragraph wrapper. */
.ai-promo-desc :global(p) {
margin: 0;
}

.ai-promo-desc :global(p + p) {
margin-top: 0.5rem;
}

.ai-promo-cta {
display: inline-flex;
align-items: center;
gap: 0.3rem;
margin-top: 0.875rem;
font-size: 0.875rem;
font-weight: 600;
color: #6720ff;
}

:global(.dark) .ai-promo-cta {
color: #a78bfa;
}

.ai-promo-arrow {
font-size: 1.1em;
line-height: 1;
transition: transform 0.2s ease;
}

a.ai-promo:hover .ai-promo-arrow {
transform: translateX(0.2rem);
}

@media (max-width: 640px) {
a.ai-promo {
gap: 1rem;
padding: 1.125rem;
}

.ai-promo-icon {
width: 40px;
height: 40px;
}

.ai-promo-icon svg {
width: 22px;
height: 22px;
}

.ai-promo-title {
font-size: 1rem;
}
}

/* A decorative twinkle and a lift are exactly the motion this setting drops. */
@media (prefers-reduced-motion: reduce) {
.ai-promo-star {
animation: none;
}

a.ai-promo,
.ai-promo-arrow {
transition: none;
}

a.ai-promo:hover {
transform: none;
}
}
</style>
7 changes: 6 additions & 1 deletion src/components/Callout.astro
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
---
interface Props {
type?: string;
title?: string | false;
}

const { type, title = false } = Astro.props;

// Map types to premium styling with CSS classes for dark mode
Expand Down Expand Up @@ -33,7 +38,7 @@ const styles = {
}
};

const style = styles[type] || styles.note;
const style = (type && styles[type as keyof typeof styles]) || styles.note;
---

<div class={`callout ${style.className}`}>
Expand Down
Loading
Loading