Skip to content

Instantly share code, notes, and snippets.

@Markus-ipse
Last active April 15, 2026 12:02
Show Gist options
  • Select an option

  • Save Markus-ipse/3fc48929c2b8ddc2701f6af5365f6c89 to your computer and use it in GitHub Desktop.

Select an option

Save Markus-ipse/3fc48929c2b8ddc2701f6af5365f6c89 to your computer and use it in GitHub Desktop.
Howwe — AI button explorations (6 variants, light + dark)
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Howwe — AI Button Explorations</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<style>
:root {
--space: 16px;
--radius: 12px;
}
body {
margin: 0;
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Mulish, Roboto,
sans-serif;
background: #f4f5f8;
}
.page {
display: grid;
grid-template-columns: 1fr 1fr;
min-height: 100vh;
}
@media (max-width: 900px) {
.page {
grid-template-columns: 1fr;
}
}
.pane {
padding: 32px;
overflow-x: hidden;
}
.pane h1 {
margin: 0 0 8px;
font-size: 22px;
font-weight: 700;
}
.pane p.intro {
margin: 0 0 24px;
font-size: 13px;
line-height: 1.5;
max-width: 56ch;
}
/* light pane */
.pane--light {
--surface: #ffffff;
--main-font: #1f2937;
--secondary-font: #6b7280;
--divider: #e5e7eb;
--frames-and-lines: #d1d5db;
--disabled-fonts: #9ca3af;
--disabled-lines: #e5e7eb;
--action-blue: #1582c5;
--status-future-purple: #9427ca;
background: #f4f5f8;
color: var(--main-font);
}
/* dark pane */
.pane--dark {
--surface: #1f2230;
--main-font: #e5e7eb;
--secondary-font: #9ca3af;
--divider: #2d3142;
--frames-and-lines: #3a3f55;
--disabled-fonts: #6b7280;
--disabled-lines: #3a3f55;
--action-blue: #5ab3e8;
--status-future-purple: #c895ff;
background: #14161f;
color: var(--main-font);
}
.AiButtonLab {
overflow-x: hidden;
--ai-grad-start: var(--action-blue);
--ai-grad-end: var(--status-future-purple);
--ai-grad-mid: color-mix(
in oklch,
var(--ai-grad-start),
var(--ai-grad-end)
);
--ai-gradient: linear-gradient(
120deg,
var(--ai-grad-start) 0%,
var(--ai-grad-mid) 50%,
var(--ai-grad-end) 100%
);
--ai-soft-bg: color-mix(in srgb, var(--ai-grad-end) 8%, transparent);
--ai-soft-border: color-mix(
in srgb,
var(--ai-grad-end) 35%,
transparent
);
--ai-glow: 0 0 0 3px
color-mix(in srgb, var(--ai-grad-end) 18%, transparent);
}
.AiShimmerIcon__stopA {
stop-color: var(--action-blue);
}
.AiShimmerIcon__stopB {
stop-color: var(--status-future-purple);
}
.AiButtonLab__variantGrid {
display: grid;
grid-template-columns: 1fr;
gap: 16px;
}
.AiButtonLab__variant {
display: flex;
flex-direction: column;
gap: 8px;
padding: 16px;
border: 1px solid var(--divider);
border-radius: var(--radius);
background: var(--surface);
}
.AiButtonLab__variantTitle {
font-size: 14px;
font-weight: 600;
margin: 0;
}
.AiButtonLab__variantDesc {
font-size: 12px;
color: var(--secondary-font);
margin: 0;
}
.AiButtonLab__row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
}
.AiBtn {
position: relative;
display: inline-flex;
align-items: center;
gap: 6px;
height: 32px;
padding: 0 14px;
border-radius: 999px;
font-weight: 600;
font-size: 13px;
border: 1px solid transparent;
background: transparent;
cursor: pointer;
transition: all 0.2s ease;
line-height: 1;
}
.AiIconBtn {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border-radius: 8px;
border: 1px solid transparent;
background: transparent;
cursor: pointer;
padding: 0;
transition: all 0.2s ease;
}
/* 1. Fill */
.AiBtn--fill,
.AiIconBtn--fill {
color: #fff;
background: var(--ai-gradient);
background-size: 200% 200%;
background-position: 0% 50%;
}
.AiBtn--fill:hover,
.AiIconBtn--fill:hover {
background-position: 100% 50%;
box-shadow: var(--ai-glow);
}
/* 2. Outline (animated) */
.AiBtn--outline {
color: transparent;
background: var(--surface);
background-image:
linear-gradient(var(--surface), var(--surface)),
linear-gradient(
120deg,
var(--ai-grad-start) 0%,
var(--ai-grad-end) 25%,
var(--ai-grad-start) 50%,
var(--ai-grad-end) 75%,
var(--ai-grad-start) 100%
);
background-size:
100% 100%,
300% 100%;
background-position:
0 0,
0% 50%;
background-origin: border-box;
background-clip: padding-box, border-box;
animation: ai-outline-sweep 3s linear infinite;
}
.AiBtn--outline .AiBtn__label {
line-height: 1.4;
padding: 2px 0;
background: linear-gradient(
120deg,
var(--ai-grad-start) 0%,
var(--ai-grad-end) 25%,
var(--ai-grad-start) 50%,
var(--ai-grad-end) 75%,
var(--ai-grad-start) 100%
);
background-size: 300% 100%;
background-position: 0% 50%;
background-clip: text;
color: transparent;
font-weight: 700;
animation: ai-outline-label-sweep 3s linear infinite;
}
.AiIconBtn--outline:hover {
background: var(--ai-soft-bg);
}
@keyframes ai-outline-sweep {
0% {
background-position:
0 0,
0% 50%;
}
100% {
background-position:
0 0,
300% 50%;
}
}
@keyframes ai-outline-label-sweep {
0% {
background-position: 0% 50%;
}
100% {
background-position: 300% 50%;
}
}
/* 3. Tint */
.AiBtn--tint,
.AiIconBtn--tint {
background: var(--ai-soft-bg);
}
.AiBtn--tint .AiBtn__label {
line-height: 1.4;
padding: 2px 0;
background: var(--ai-gradient);
background-clip: text;
color: transparent;
font-weight: 700;
}
.AiBtn--tint:hover,
.AiIconBtn--tint:hover {
background: color-mix(in srgb, var(--ai-grad-mid) 14%, transparent);
}
/* 4. Shimmer */
.AiBtn--shimmer,
.AiIconBtn--shimmer,
.AiBtn--shimmerSoft,
.AiIconBtn--shimmerSoft {
overflow: hidden;
color: #fff;
background: var(--ai-gradient);
}
.AiBtn--shimmer::after,
.AiIconBtn--shimmer::after {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(
100deg,
transparent 35%,
rgb(255 255 255 / 55%) 50%,
transparent 65%
);
transform: translateX(-100%);
animation: ai-shimmer 2.6s ease-in-out infinite;
}
/* 4.1 Softer shimmer — narrower stripe, lower opacity, slower sweep */
.AiBtn--shimmerSoft::after,
.AiIconBtn--shimmerSoft::after {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(
100deg,
transparent 42%,
rgb(255 255 255 / 22%) 50%,
transparent 58%
);
transform: translateX(-100%);
animation: ai-shimmer-soft 5.2s ease-in-out infinite;
}
@keyframes ai-shimmer-soft {
0% {
transform: translateX(-100%);
}
30%,
100% {
transform: translateX(100%);
}
}
@keyframes ai-shimmer {
0% {
transform: translateX(-100%);
}
60%,
100% {
transform: translateX(100%);
}
}
/* 5. Underline */
.AiBtn--underline {
color: var(--main-font);
background: var(--surface);
border: 1px solid var(--divider);
overflow: hidden;
}
.AiBtn--underline::after,
.AiIconBtn--underline::after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 2px;
background: var(--ai-gradient);
background-size: 200% 100%;
animation: ai-underline 2.8s ease-in-out infinite;
}
.AiIconBtn--underline {
overflow: hidden;
}
.AiBtn--underline:hover,
.AiIconBtn--underline:hover {
background: var(--ai-soft-bg);
border-color: var(--ai-soft-border);
}
@keyframes ai-underline {
0% {
background-position: 0% 50%;
}
100% {
background-position: 200% 50%;
}
}
/* Milestone form mock */
.MsForm {
margin-top: 24px;
padding: 20px;
max-width: 520px;
border: 1px solid var(--divider);
border-radius: 8px;
background: var(--surface);
}
.MsForm h2 {
margin: 0 0 4px;
font-size: 18px;
font-weight: 700;
}
.MsForm__hint {
margin: 0 0 20px;
font-size: 12px;
color: var(--secondary-font);
}
.MsForm__row {
display: flex;
align-items: flex-start;
gap: 12px;
margin-bottom: 16px;
}
.MsForm__icon {
width: 20px;
height: 20px;
color: var(--secondary-font);
flex-shrink: 0;
margin-top: 26px;
}
.MsForm__field {
flex: 1;
display: flex;
flex-direction: column;
gap: 6px;
}
.MsForm__label {
font-size: 12px;
font-weight: 600;
color: var(--main-font);
}
.MsForm__input,
.MsForm__textarea,
.MsForm__select {
width: 100%;
padding: 6px 10px;
border: 1px solid var(--frames-and-lines);
border-radius: 4px;
background: var(--surface);
color: var(--main-font);
font: inherit;
}
.MsForm__input.is-title {
font-size: 18px;
font-weight: 700;
}
.MsForm__textarea {
min-height: 90px;
resize: vertical;
}
.MsForm__aiRow {
display: flex;
gap: 4px;
margin-top: 8px;
}
.MsForm__helper {
width: 28px;
height: 28px;
border: none;
border-radius: 6px;
background: transparent;
color: var(--action-blue);
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
}
.MsForm__helper:hover {
background: color-mix(in srgb, var(--action-blue) 10%, transparent);
}
.MsForm__footer {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 16px;
}
.MsForm__btn {
padding: 6px 14px;
border-radius: 4px;
border: 1px solid var(--frames-and-lines);
background: var(--surface);
color: var(--main-font);
cursor: pointer;
}
.MsForm__btn--primary {
background: var(--action-blue);
border-color: var(--action-blue);
color: #fff;
}
/* Shimmering sparkles icon — gradient fill animates across */
.ShimmerSparkles {
width: 20px;
height: 20px;
overflow: hidden;
flex-shrink: 0;
filter: drop-shadow(
0 0 2px color-mix(in srgb, var(--status-future-purple) 45%, transparent)
);
}
/* Ghost AI button (icon only, shimmering fill) */
.AiGhostBtn {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border-radius: 6px;
border: 1px solid transparent;
background: transparent;
cursor: pointer;
padding: 0;
transition: background 0.15s ease;
}
.AiGhostBtn:hover {
background: var(--ai-soft-bg);
}
/* 6. Pulse */
.AiBtn--pulse {
color: var(--ai-grad-mid);
background: var(--surface);
border: 1px dashed var(--ai-soft-border);
animation: ai-pulse 2.4s ease-in-out infinite;
}
.AiIconBtn--pulse {
animation: ai-pulse 2.4s ease-in-out infinite;
}
.AiBtn--pulse:hover,
.AiIconBtn--pulse:hover {
animation-play-state: paused;
background: var(--ai-soft-bg);
}
.AiBtn--pulse:hover {
border-color: var(--ai-grad-mid);
}
@keyframes ai-pulse {
0%,
100% {
box-shadow: 0 0 0 0
color-mix(in srgb, var(--ai-grad-mid) 30%, transparent);
}
50% {
box-shadow: 0 0 0 6px
color-mix(in srgb, var(--ai-grad-mid) 0%, transparent);
}
}
</style>
</head>
<body>
<!-- shared shimmer gradient defs (one per pane so each picks up its own tokens) -->
<svg
width="0"
height="0"
style="position: absolute"
aria-hidden="true"
></svg>
<div class="page">
<section class="pane pane--light">
<h1>Light mode</h1>
<p class="intro">
Six exploratory designs for AI feature buttons. Gradient uses
existing Howwe tokens (<code>--action-blue</code> →
<code>--status-future-purple</code>). Icon strokes shimmer with an
animated SVG gradient.
</p>
<div class="AiButtonLab" id="lab-light"></div>
<div class="AiButtonLab" id="form-light"></div>
</section>
<section class="pane pane--dark">
<h1>Dark mode</h1>
<p class="intro">
Same six variants, dark surface tokens. The gradient automatically
rebalances because both endpoints are theme tokens.
</p>
<div class="AiButtonLab" id="lab-dark"></div>
<div class="AiButtonLab" id="form-dark"></div>
</section>
</div>
<script>
const variants = [
{
id: 'fill',
title: '1. Gradient fill',
desc: 'Solid statement — strongest call-out.',
white: true,
},
{
id: 'outline',
title: '2. Gradient outline',
desc: 'Animated gradient border + shimmering label.',
},
{
id: 'tint',
title: '3. Soft tint',
desc: 'Tinted background with gradient label/icon.',
},
{
id: 'shimmer',
title: '4. Shimmer',
desc: 'Gradient fill with continuous highlight sweep.',
white: true,
},
{
id: 'shimmerSoft',
title: '4.1 Shimmer (soft)',
desc: 'Same idea as #4 but narrower stripe and lower opacity — reads as a gentle shine instead of a spotlight pass.',
white: true,
},
{
id: 'underline',
title: '5. Gradient underline',
desc: 'Neutral pill with gradient bar at the bottom.',
},
{
id: 'pulse',
title: '6. Breathing pulse',
desc: 'Neutral body with a gentle expanding glow.',
},
];
const paths = {
thunder: '<path d="M13 2 L3 14 h9 l-1 8 10-12 h-9 l1-8 z"/>',
bulb: '<path d="M9 18h6"/><path d="M10 22h4"/><path d="M12 2a7 7 0 0 0-4 12.5V18h8v-3.5A7 7 0 0 0 12 2z"/>',
};
function shimmerSvg(pathHtml, white, gradId) {
const stroke = white ? '#fff' : `url(#${gradId})`;
return `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="${stroke}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="overflow:hidden;flex-shrink:0">${pathHtml}</svg>`;
}
function defs(gradId) {
return `<svg width="0" height="0" style="position:absolute" aria-hidden="true"><defs><linearGradient id="${gradId}" gradientUnits="userSpaceOnUse" x1="-12" y1="0" x2="0" y2="0"><stop class="AiShimmerIcon__stopA" offset="0%"/><stop class="AiShimmerIcon__stopB" offset="50%"/><stop class="AiShimmerIcon__stopA" offset="100%"/><animate attributeName="x1" values="-24;24" dur="2.4s" repeatCount="indefinite"/><animate attributeName="x2" values="0;48" dur="2.4s" repeatCount="indefinite"/></linearGradient></defs></svg>`;
}
function renderLab(targetId, gradId) {
const lab = document.getElementById(targetId);
const html =
defs(gradId) +
'<div class="AiButtonLab__variantGrid">' +
variants
.map(
(v) => `
<div class="AiButtonLab__variant">
<h4 class="AiButtonLab__variantTitle">${v.title}</h4>
<p class="AiButtonLab__variantDesc">${v.desc}</p>
<div class="AiButtonLab__row">
<button class="AiBtn AiBtn--${v.id}">
${shimmerSvg(paths.thunder, v.white, gradId).replace('width="18" height="18"', 'width="16" height="16"')}
<span class="AiBtn__label">Suggest with AI</span>
</button>
<button class="AiIconBtn AiIconBtn--${v.id}" aria-label="Suggest">
${shimmerSvg(paths.thunder, v.white, gradId)}
</button>
<button class="AiIconBtn AiIconBtn--${v.id}" aria-label="Suggest">
${shimmerSvg(paths.bulb, v.white, gradId)}
</button>
</div>
</div>`,
)
.join('') +
'</div>';
lab.innerHTML = html;
}
renderLab('lab-light', 'ai-shimmer-grad-light');
renderLab('lab-dark', 'ai-shimmer-grad-dark');
function shimmerSparkles(gradId) {
return `
<svg viewBox="0 0 24 24" class="ShimmerSparkles" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="${gradId}" gradientUnits="userSpaceOnUse" x1="-12" y1="0" x2="0" y2="0">
<stop offset="0%" stop-color="var(--action-blue)"/>
<stop offset="50%" stop-color="var(--status-future-purple)"/>
<stop offset="100%" stop-color="var(--action-blue)"/>
<animate attributeName="x1" values="-24;24" dur="2.4s" repeatCount="indefinite"/>
<animate attributeName="x2" values="0;48" dur="2.4s" repeatCount="indefinite"/>
</linearGradient>
</defs>
<path fill="url(#${gradId})" d="M12 1.5 L14.5 9.5 L22.5 12 L14.5 14.5 L12 22.5 L9.5 14.5 L1.5 12 L9.5 9.5 Z M19.5 0 L20.5 3.5 L24 4.5 L20.5 5.5 L19.5 9 L18.5 5.5 L15 4.5 L18.5 3.5 Z"/>
</svg>
`;
}
function renderForm(targetId, gradId) {
const el = document.getElementById(targetId);
el.innerHTML = `
<div class="MsForm">
<h2>New milestone</h2>
<p class="MsForm__hint">In-context preview of the shimmering AI suggest button inside the milestone form.</p>
<div class="MsForm__row">
<svg class="MsForm__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6h16"/><path d="M4 12h16"/><path d="M4 18h10"/></svg>
<div class="MsForm__field">
<label class="MsForm__label">Title</label>
<input class="MsForm__input is-title" value="Ship onboarding revamp"/>
</div>
</div>
<div class="MsForm__row">
<svg class="MsForm__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M7 9h10"/><path d="M7 13h10"/><path d="M7 17h6"/></svg>
<div class="MsForm__field">
<label class="MsForm__label">Definition of done</label>
<textarea class="MsForm__textarea" placeholder="Describe what needs to be true…"></textarea>
<div class="MsForm__aiRow">
<button class="AiGhostBtn" title="Suggest with AI">
${shimmerSparkles(gradId)}
</button>
<button class="MsForm__helper" title="Polish text">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 20 L14 10"/><path d="M14 4l6 6-4 4-6-6z"/></svg>
</button>
<button class="MsForm__helper" title="Undo">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 14L4 9l5-5"/><path d="M4 9h10a6 6 0 0 1 0 12h-3"/></svg>
</button>
</div>
</div>
</div>
<div class="MsForm__row">
<svg class="MsForm__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="4"/><path d="M4 20a7 7 0 0 1 14 0"/></svg>
<div class="MsForm__field">
<label class="MsForm__label">Owner</label>
<select class="MsForm__select">
<option>Alice Johnson</option>
<option>Bob Smith</option>
</select>
</div>
</div>
<div class="MsForm__row">
<svg class="MsForm__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M8 3v4"/><path d="M16 3v4"/><path d="M3 10h18"/></svg>
<div class="MsForm__field">
<label class="MsForm__label">Deadline</label>
<input class="MsForm__input" value="2026-05-15"/>
</div>
</div>
<div class="MsForm__footer">
<button class="MsForm__btn">Cancel</button>
<button class="MsForm__btn MsForm__btn--primary">Create milestone</button>
</div>
</div>
`;
}
renderForm('form-light', 'form-sparkles-light');
renderForm('form-dark', 'form-sparkles-dark');
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment