Files
hyperframes/registry/components/touch-indicator/demo.html
Miguel Ángel 590c192498 feat(catalog): add 3D objects, cursors, snow, starfield and paper transitions (#4349)
* feat(catalog): shelve 3D objects and cursors in their own catalog sections

Carries the generator grouping rules from #4310 and #4308.

* feat(registry): add a starfield ambient overlay

From #4313.

* feat(registry): add a falling snow ambient overlay

From #4331.

* feat(registry): add a torn-paper-rip transition

From #4321.

* feat(registry): add a paper-collage-reveal transition

From #4322.

* feat(registry): add a curtain-reveal transition

From #4323.

* feat(registry): add 3D objects: product turntable, glass hero, exploded stack, extruded logo

From #4310.

* feat(registry): add eight cursor animations and the dart cursor

From #4308.

* fix(catalog): find items by everyday editor words and accept a bare search word

From #4311.

* chore(registry): remove the simulated-cursor component

The Cursors section's new pointer animations replace it (from #4308).

* fix(registry): take cursor-zoom-follow's screenshot from runtime variables only

The declared default is empty, so page markup never becomes an image URL.

Clears the code-scanning alert raised on #4308.

* docs(cli): show a catalog search example that finds items

"smoke" matches nothing in the catalog; "crossfade" finds fade-through.

* chore(ci): allow the simulated-cursor removal in the deletion guard

* fix(ci): skip tracked files missing from disk in the player CDN pin check

Catalog regeneration removes a deleted item's pages before the publish PR untracks them,

so the check crashed on every catalog deletion PR.

* chore(cli): drop comments that narrate catalog search history

* fix(registry): drag-drop keeps the family arrow and shows the drop slot only while hovering

* fix(registry): agent cursor labels its steps in an accent tag, no glow or sparkles, in 4.8 s

* fix(registry): annotate cursor draws a tapered marker stroke and clears the circled value

* fix(registry): show real file thumbnails and end the context menu demo sooner

* fix(registry): give marquee select real photos, a Finder-blue marquee and a tighter loop

* fix(registry): centre the text-select toolbar on the selection and tighten its timing

* fix(registry): selection cursors default to the macOS system blue

* style(registry): format the agent and annotate cursor sources

* fix(registry): dart-cursor stops covering the choice it makes and shows the export finishing

* fix(registry): frame the product turntable fully and light it with two studio softboxes

* fix(registry): touch-indicator taps like a real phone and the post it taps gets liked

* fix(registry): press-ripple confirms with a check and small click rings at the cursor

* fix(registry): exploded stack separates at once and marks each layer in blue as it is labelled

* fix(registry): extruded logo settles facing the camera, set in Geist Bold with a neutral rim

* fix(registry): magnetic cursor docks its toolbar on the selection and ends after the press

* style(registry): format the exploded stack source

* fix(registry): glyph trail becomes a tutorial cursor path with numbered steps on a settings screen

* fix(registry): make glass-hero a clear lens that shows the headline through it

* fix(registry): snow reads as real snowfall with bokeh flakes over a winter title card

* fix(registry): oversized cursor is a 3.5x arrow that clicks a real editor toggle on

* fix(registry): starfield reads as a drifting night sky over a real title card

* style(registry): format the glass hero source

* fix(registry): make the paper tear show a white fibrous edge and lift the torn sheet away

* fix(registry): curtain reveal draws velvet folds that gather onto a title card

* fix(registry): make every zoom-follow click change the screen and read at zoom

* fix(registry): paper collage pieces stay opaque, tear with deckled grain and lift away on an arc

* refactor(registry): drop dead code and correct stale comments in reworked items

* fix(registry): agent cursor scrubs cleanly, docs match press-ripple, fictional demo names

* docs(registry): catalog notes say oversized-cursor exits and how it differs from press-ripple

* docs(registry): plain lowercase comments in oversized-cursor and touch-indicator

* docs(registry): oversized-cursor names CLICK_AT as the click cue
2026-09-28 22:03:59 +00:00

440 lines
14 KiB
HTML
Vendored

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Touch Indicator - Demo</title>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
* {
box-sizing: border-box;
}
html,
body {
margin: 0;
width: 1920px;
height: 1080px;
overflow: hidden;
}
/*
Host declares the 17-token theme contract; the phone UI below reads
these via var(--token, fallback).
*/
#demo {
width: 1920px;
height: 1080px;
display: grid;
place-items: center;
background: #111318;
--bg: #0b0c10;
--fg: #f5f6f8;
--muted: #8b8f9a;
--surface: #16181d;
--border: #262931;
--brand: #e4e4e7;
--accent: #e4e4e7;
--font-display: Inter, system-ui, sans-serif;
--font-body: Inter, system-ui, sans-serif;
--font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
--radius: 20px;
--space-1: 8px;
--space-2: 16px;
--space-3: 24px;
--dur-beat: 0.4s;
--ease-standard: cubic-bezier(0.2, 0, 0, 1);
--ease-emphasis: cubic-bezier(0.05, 0.7, 0.1, 1);
}
.demo-frame {
display: grid;
place-items: center;
padding: 56px;
transform: scale(1.3); /* ponytail: scale wrapper only, GSAP targets stay untouched */
transform-origin: center;
}
.phone {
position: relative;
width: 380px;
height: 760px;
border-radius: 44px;
background: var(--bg, #0b0c10);
border: 1px solid color-mix(in srgb, var(--fg, #fff) 12%, transparent);
box-shadow: 0 40px 90px rgba(0, 0, 0, 0.55);
overflow: hidden;
font-family: var(--font-body, sans-serif);
color: var(--fg, #f5f6f8);
}
.phone__status {
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 28px;
font-size: 15px;
font-weight: 600;
font-variant-numeric: tabular-nums;
}
.phone__status svg {
display: block;
fill: currentColor;
}
.phone__header {
height: 50px;
display: flex;
align-items: center;
padding: 0 16px;
font-family: var(--font-display, sans-serif);
font-weight: 700;
font-size: 22px;
letter-spacing: -0.02em;
}
.post__head {
height: 54px;
display: flex;
align-items: center;
gap: 10px;
padding: 0 14px;
font-size: 13px;
line-height: 16px;
}
.avatar {
width: 32px;
height: 32px;
border-radius: 50%;
display: grid;
place-items: center;
font-size: 12px;
font-weight: 600;
color: #fff;
flex: none;
}
.post__user {
font-weight: 600;
}
.post__place {
color: var(--muted, #8b8f9a);
font-size: 12px;
}
.post__more {
margin-left: auto;
color: var(--fg, #f5f6f8);
font-size: 16px;
letter-spacing: 1px;
}
.post__photo {
display: block;
width: 380px;
height: 380px;
}
.post__actions {
height: 46px;
display: flex;
align-items: center;
padding: 0 8px 0 16px;
font-size: 14px;
font-weight: 600;
font-variant-numeric: tabular-nums;
}
.icon {
width: 26px;
height: 26px;
fill: none;
stroke: currentColor;
stroke-width: 1.8;
stroke-linecap: round;
stroke-linejoin: round;
flex: none;
}
.like-btn {
position: relative;
width: 44px;
height: 44px;
margin-left: -9px;
border: 0;
padding: 0;
display: grid;
place-items: center;
background: none;
color: var(--fg, #f5f6f8);
}
.like-btn .icon {
grid-area: 1 / 1;
}
.like-btn__filled {
fill: #ff3b5c;
stroke: #ff3b5c;
opacity: 0;
}
.post__stat {
margin: 0 18px 0 2px;
}
.post__save {
margin-left: auto;
margin-right: 8px;
}
.post__caption {
padding: 2px 16px 0;
font-size: 14px;
line-height: 19px;
text-wrap: pretty;
}
.post__meta {
margin-top: 4px;
color: var(--muted, #8b8f9a);
font-size: 13px;
}
.post--next {
margin-top: 14px;
}
.phone__tabs {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 56px;
display: flex;
align-items: center;
justify-content: space-around;
padding-bottom: 6px;
background: var(--bg, #0b0c10);
border-top: 1px solid var(--border, #262931);
}
.phone__tabs .avatar {
width: 26px;
height: 26px;
font-size: 10px;
}
/*
Mount slot: fills the whole phone screen, so touch-indicator.html's
18cqw disc resolves against the 380px screen (68px), not the frame.
*/
.touch-indicator-mount {
position: absolute;
inset: 0;
}
</style>
</head>
<body>
<div
id="demo"
data-composition-id="touch-indicator-demo"
data-start="0"
data-width="1920"
data-height="1080"
data-duration="1.5"
>
<div class="demo-frame">
<div class="phone">
<div class="phone__status">
<span>9:41</span>
<svg width="44" height="12" viewBox="0 0 44 12" aria-hidden="true">
<rect x="0" y="8" width="3" height="4" rx="1" />
<rect x="5" y="6" width="3" height="6" rx="1" />
<rect x="10" y="3" width="3" height="9" rx="1" />
<rect x="15" y="0" width="3" height="12" rx="1" />
<rect
x="23.5"
y="0.5"
width="18"
height="11"
rx="3"
fill="none"
stroke="currentColor"
opacity="0.45"
/>
<rect x="25.5" y="2.5" width="12" height="7" rx="1.5" />
<rect x="42.5" y="4" width="1.5" height="4" rx="0.75" opacity="0.45" />
</svg>
</div>
<div class="phone__header">Feed</div>
<div class="post__head">
<div class="avatar" style="background: #b8643f">ML</div>
<div>
<div class="post__user">maya.lin</div>
<div class="post__place">Lago di Braies, Italy</div>
</div>
<span class="post__more">&middot;&middot;&middot;</span>
</div>
<svg class="post__photo" viewBox="0 0 380 380" aria-label="Dawn over a mountain lake">
<defs>
<linearGradient id="sky" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#1d2640" />
<stop offset="0.5" stop-color="#5d4f73" />
<stop offset="0.82" stop-color="#d98e6e" />
<stop offset="1" stop-color="#f4c794" />
</linearGradient>
<linearGradient id="lake" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#d99a7c" />
<stop offset="0.3" stop-color="#6e5470" />
<stop offset="1" stop-color="#141827" />
</linearGradient>
<filter id="soft" x="-1" y="-1" width="3" height="3">
<feGaussianBlur stdDeviation="8" />
</filter>
<filter id="grain">
<feTurbulence type="fractalNoise" baseFrequency="0.85" numOctaves="2" seed="7" />
<feColorMatrix values="0 0 0 0 0.5 0 0 0 0 0.5 0 0 0 0 0.5 0 0 0 0.9 0" />
</filter>
<g id="range">
<path
d="M0 196 L22 184 L40 190 L66 162 L80 170 L96 148 L112 164 L128 158 L150 182 L176 172 L198 186 L222 178 L244 198 L262 207 L284 192 L300 172 L316 154 L332 164 L350 142 L366 158 L380 152 L380 240 L0 240 Z"
fill="#8e6a80"
/>
<path
d="M0 214 L30 201 L54 208 L90 187 L118 204 L150 197 L180 213 L230 223 L272 227 L310 211 L340 197 L362 205 L380 199 L380 240 L0 240 Z"
fill="#473a56"
/>
<path
d="M0 240 L0 224 L6 214 L11 226 L17 206 L24 227 L31 216 L37 229 L46 212 L53 231 L62 222 L70 234 L300 235 L309 222 L316 232 L324 208 L332 230 L340 216 L347 231 L356 204 L364 229 L372 214 L380 226 L380 240 Z"
fill="#151826"
/>
</g>
</defs>
<rect width="380" height="240" fill="url(#sky)" />
<circle cx="262" cy="200" r="34" fill="#ffd9ad" opacity="0.6" filter="url(#soft)" />
<circle cx="262" cy="200" r="12" fill="#fff2d9" />
<use href="#range" />
<rect y="240" width="380" height="140" fill="url(#lake)" />
<use href="#range" transform="matrix(1 0 0 -1 0 480)" opacity="0.35" />
<ellipse
cx="262"
cy="268"
rx="9"
ry="30"
fill="#ffd9ad"
opacity="0.55"
filter="url(#soft)"
/>
<path
d="M0 380 L0 334 C60 324 108 338 150 346 C188 354 214 366 236 380 Z"
fill="#0f111b"
/>
<rect width="380" height="380" filter="url(#grain)" opacity="0.07" />
</svg>
<div class="post__actions">
<button class="like-btn" type="button" aria-label="Like">
<svg class="icon" viewBox="0 0 24 24" aria-hidden="true">
<path
d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"
/>
</svg>
<svg class="icon like-btn__filled" viewBox="0 0 24 24" aria-hidden="true">
<path
d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"
/>
</svg>
</button>
<span class="post__stat like-count">128</span>
<svg class="icon" viewBox="0 0 24 24" aria-hidden="true">
<path
d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"
/>
</svg>
<span class="post__stat">14</span>
<svg class="icon" viewBox="0 0 24 24" aria-hidden="true">
<path d="M22 2 11 13M22 2l-7 20-4-9-9-4z" />
</svg>
<svg class="icon post__save" viewBox="0 0 24 24" aria-hidden="true">
<path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z" />
</svg>
</div>
<div class="post__caption">
<span class="post__user">maya.lin</span> First light over the lake. Worth the
4&nbsp;a.m. alarm.
<div class="post__meta">View all 14 comments</div>
<div class="post__meta">2 hours ago</div>
</div>
<div class="post__head post--next">
<div class="avatar" style="background: #3f6b58">NC</div>
<div>
<div class="post__user">northside.coffee</div>
<div class="post__place">Portland, Oregon</div>
</div>
<span class="post__more">&middot;&middot;&middot;</span>
</div>
<div class="phone__tabs">
<svg class="icon" viewBox="0 0 24 24" aria-hidden="true">
<path
d="M3 10.5 12 3l9 7.5V20a1 1 0 0 1-1 1h-5v-6h-6v6H4a1 1 0 0 1-1-1z"
fill="currentColor"
/>
</svg>
<svg class="icon" viewBox="0 0 24 24" aria-hidden="true">
<circle cx="11" cy="11" r="7" />
<path d="m20 20-3.5-3.5" />
</svg>
<svg class="icon" viewBox="0 0 24 24" aria-hidden="true">
<rect x="3" y="3" width="18" height="18" rx="5" />
<path d="M12 8v8M8 12h8" />
</svg>
<svg class="icon" viewBox="0 0 24 24" aria-hidden="true">
<rect x="3" y="3" width="18" height="18" rx="5" />
<path d="M10 9v6l5-3z" />
</svg>
<div class="avatar" style="background: #5a5f8a">JR</div>
</div>
<!--
MOUNT: touch-indicator as a real sub-composition clip, not
pasted markup. This div's data-composition-id must match the
internal data-composition-id inside touch-indicator.html (and
the window.__timelines key it registers under) -- see
sub-compositions.md, Pitfall 2. targetX/Y re-aim the primitive's
own default landing point (50/55) at the like button's center
(29px, 551px = 7.63%, 72.5% of the 380x760 mount box).
-->
<div
id="touch-indicator-mount"
class="clip touch-indicator-mount"
data-composition-id="touch-indicator"
data-composition-src="./touch-indicator.html"
data-variable-values='{"targetX":7.63,"targetY":72.5}'
data-start="0.4"
data-duration="1.1"
data-track-index="1"
data-width="380"
data-height="760"
></div>
</div>
</div>
</div>
<script>
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
// Touch-down: the mount's data-start (0.4) + the disc's 4-frame appear.
var TAP = 0.4 + 4 / 60;
tl.set(".like-btn__filled", { opacity: 1 }, TAP);
tl.set(".like-count", { textContent: "129" }, TAP);
tl.fromTo(
".like-btn",
{ scale: 1 },
{ scale: 1.2, duration: 0.1, ease: "power2.out", immediateRender: false },
TAP,
);
tl.to(".like-btn", { scale: 1, duration: 0.15, ease: "power2.out" }, TAP + 0.1);
window.__timelines["touch-indicator-demo"] = tl;
</script>
</body>
</html>