docs: add texture mask text catalog entry (#650)
* feat(registry): add texture mask PNGs for texture-mask-text component * feat(registry): add texture-mask-text CSS snippet * feat(registry): add registry-item.json for texture-mask-text * feat(registry): add texture-mask-text demo composition * feat(registry): register texture-mask-text component in manifest * style: format texture-mask-text files with oxfmt * fix: set mask-image directly on texture classes instead of via CSS custom property url() inside CSS custom properties doesn't resolve correctly with mask-image in some browsers. Move mask-image declarations to each texture class directly. * docs: add texture mask text catalog entry * test: lint texture mask text usage * fix: harden texture mask text docs and lint * fix: stabilize texture mask asset paths * fix: address texture catalog review feedback * fix: harden texture mask text instructions * docs: remove texture catalog intro copy * docs: use canonical texture preview URL * docs: use cdn texture mask assets * fix: escape catalog frontmatter safely * test: stabilize windows render cli test * test: pin texture catalog instructions
@@ -0,0 +1,487 @@
|
||||
---
|
||||
title: "Texture Mask Text"
|
||||
---
|
||||
|
||||
`text` `texture` `mask` `effect`
|
||||
|
||||
<div className="hf-texture-preview-panel">
|
||||
<div className="hf-texture-preview-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/brick.png')" }}>
|
||||
<div className="hf-texture-preview-label">Brick</div>
|
||||
<div className="hf-texture-preview-shadow"><div className="hf-texture-preview-word">BRICK</div></div>
|
||||
</div>
|
||||
<div className="hf-texture-preview-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/rock.png')" }}>
|
||||
<div className="hf-texture-preview-label">Rock</div>
|
||||
<div className="hf-texture-preview-shadow"><div className="hf-texture-preview-word">ROCK</div></div>
|
||||
</div>
|
||||
<div className="hf-texture-preview-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/ground-103.png')" }}>
|
||||
<div className="hf-texture-preview-label">Ground 103</div>
|
||||
<div className="hf-texture-preview-shadow"><div className="hf-texture-preview-word">GROUND</div></div>
|
||||
</div>
|
||||
<div className="hf-texture-preview-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/wood.png')" }}>
|
||||
<div className="hf-texture-preview-label">Wood</div>
|
||||
<div className="hf-texture-preview-shadow"><div className="hf-texture-preview-word">WOOD</div></div>
|
||||
</div>
|
||||
<div className="hf-texture-preview-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/metal.png')" }}>
|
||||
<div className="hf-texture-preview-label">Metal</div>
|
||||
<div className="hf-texture-preview-shadow"><div className="hf-texture-preview-word">METAL</div></div>
|
||||
</div>
|
||||
<div className="hf-texture-preview-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/lava.png')" }}>
|
||||
<div className="hf-texture-preview-label">Lava</div>
|
||||
<div className="hf-texture-preview-shadow"><div className="hf-texture-preview-word">LAVA</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## Install
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```bash Terminal
|
||||
npx hyperframes add texture-mask-text
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Details
|
||||
|
||||
| Property | Value |
|
||||
| --- | --- |
|
||||
| Type | Component |
|
||||
|
||||
## Agent Usage
|
||||
|
||||
Use this wording when asking an agent to apply a texture:
|
||||
|
||||
```text
|
||||
Use the Texture Mask Text catalog component.
|
||||
|
||||
1. From the project root, run:
|
||||
npx hyperframes add texture-mask-text
|
||||
2. That command creates this installed snippet:
|
||||
compositions/components/texture-mask-text/texture-mask-text.html
|
||||
3. Open that file and paste the real <style> block
|
||||
near the bottom into the composition once. That CSS defines
|
||||
hf-texture-text and every hf-texture-* class.
|
||||
4. Apply this class to the target text:
|
||||
class="hf-texture-text hf-texture-brick"
|
||||
5. For another material, copy one hf-texture-* class
|
||||
from the Texture Examples cards.
|
||||
6. This is the proper way to apply drop shadow
|
||||
to textured text: wrap the text and put
|
||||
filter on the wrapper, not on the text.
|
||||
Use this markup:
|
||||
<div style="filter: drop-shadow(1px 2px 1px rgba(0,0,0,0.48))">
|
||||
<div class="hf-texture-text hf-texture-brick">TEXT</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
After install, the snippet lives at `compositions/components/texture-mask-text/texture-mask-text.html` inside the project where you ran `npx hyperframes add texture-mask-text`. The part to paste is the real `<style>` element near the bottom of that file; the texture PNGs install to `assets/texture-mask-text/masks/` and are referenced by project-root URLs in that CSS.
|
||||
|
||||
Swap `hf-texture-brick` for the class shown on any texture card below. The base class `hf-texture-text` is always required.
|
||||
|
||||
## Animated Texture
|
||||
|
||||
Animate the texture by moving the mask position on the text element. Keep drop shadow on a wrapper so the shadow follows the textured contour.
|
||||
|
||||
<div className="hf-texture-animate-demo" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/lava.png')" }}>
|
||||
<div className="hf-texture-animate-meta">
|
||||
<div className="hf-texture-animate-label">Animated mask position</div>
|
||||
<code className="hf-texture-animate-class">hf-texture-text hf-texture-lava</code>
|
||||
</div>
|
||||
<div className="hf-texture-animate-shadow">
|
||||
<div className="hf-texture-animate-word">MOTION</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
```html
|
||||
<div class="texture-shadow">
|
||||
<div class="hf-texture-text hf-texture-lava animated-texture">MOTION</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
```css
|
||||
.animated-texture {
|
||||
--mask-size: 180% 180%;
|
||||
--mask-position: 0% 50%;
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
tl.to(".animated-texture", {
|
||||
"--mask-position": "100% 50%",
|
||||
duration: 1.2,
|
||||
ease: "sine.inOut",
|
||||
yoyo: true,
|
||||
repeat: 1,
|
||||
}, 0);
|
||||
window.__timelines["my-composition"] = tl;
|
||||
```
|
||||
|
||||
## Texture Examples
|
||||
|
||||
<div className="hf-texture-example-groups">
|
||||
<div>
|
||||
<h3 className="hf-texture-example-title">Masonry</h3>
|
||||
<div className="hf-texture-example-grid">
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/brick.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Brick</div><code className="hf-texture-example-class">hf-texture-brick</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">BRICK</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-brick</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/bricks-104.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Bricks 104</div><code className="hf-texture-example-class">hf-texture-bricks-104</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">BRICK</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-bricks-104</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/bricks-102.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Bricks 102</div><code className="hf-texture-example-class">hf-texture-bricks-102</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">BRICK</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-bricks-102</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/bricks-101.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Bricks 101</div><code className="hf-texture-example-class">hf-texture-bricks-101</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">BRICK</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-bricks-101</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/bricks-075-a.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Bricks 075 A</div><code className="hf-texture-example-class">hf-texture-bricks-075-a</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">BRICK</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-bricks-075-a</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/concrete.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Concrete</div><code className="hf-texture-example-class">hf-texture-concrete</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">CONCRETE</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-concrete</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/concrete-034.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Concrete 034</div><code className="hf-texture-example-class">hf-texture-concrete-034</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">CONCRETE</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-concrete-034</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/concrete-047-a.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Concrete 047 A</div><code className="hf-texture-example-class">hf-texture-concrete-047-a</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">CONCRETE</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-concrete-047-a</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/concrete-046.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Concrete 046</div><code className="hf-texture-example-class">hf-texture-concrete-046</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">CONCRETE</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-concrete-046</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/concrete-042-a.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Concrete 042 A</div><code className="hf-texture-example-class">hf-texture-concrete-042-a</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">CONCRETE</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-concrete-042-a</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/plaster-001.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Plaster 001</div><code className="hf-texture-example-class">hf-texture-plaster-001</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">PLASTER</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-plaster-001</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/painted-plaster-017.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Painted Plaster 017</div><code className="hf-texture-example-class">hf-texture-painted-plaster-017</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">PLASTER</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-painted-plaster-017</code></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="hf-texture-example-title">Stone</h3>
|
||||
<div className="hf-texture-example-grid">
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/rock.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Rock</div><code className="hf-texture-example-class">hf-texture-rock</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">ROCK</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-rock</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/rock-063.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Rock 063</div><code className="hf-texture-example-class">hf-texture-rock-063</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">ROCK</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-rock-063</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/rock-058.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Rock 058</div><code className="hf-texture-example-class">hf-texture-rock-058</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">ROCK</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-rock-058</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/onyx.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Onyx</div><code className="hf-texture-example-class">hf-texture-onyx</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">ONYX</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-onyx</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/marble-012.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Marble 012</div><code className="hf-texture-example-class">hf-texture-marble-012</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">MARBLE</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-marble-012</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/marble-016.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Marble 016</div><code className="hf-texture-example-class">hf-texture-marble-016</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">MARBLE</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-marble-016</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/travertine-009.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Travertine 009</div><code className="hf-texture-example-class">hf-texture-travertine-009</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">STONE</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-travertine-009</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/paving-stones-150.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Paving Stones 150</div><code className="hf-texture-example-class">hf-texture-paving-stones-150</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">STONE</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-paving-stones-150</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/paving-stones-138.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Paving Stones 138</div><code className="hf-texture-example-class">hf-texture-paving-stones-138</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">STONE</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-paving-stones-138</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/tiles-138.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Tiles 138</div><code className="hf-texture-example-class">hf-texture-tiles-138</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">TILE</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-tiles-138</code></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="hf-texture-example-title">Ground / Road</h3>
|
||||
<div className="hf-texture-example-grid">
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/ground-103.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Ground 103</div><code className="hf-texture-example-class">hf-texture-ground-103</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">GROUND</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-ground-103</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/ground-037.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Ground 037</div><code className="hf-texture-example-class">hf-texture-ground-037</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">GROUND</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-ground-037</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/ground-054.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Ground 054</div><code className="hf-texture-example-class">hf-texture-ground-054</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">GROUND</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-ground-054</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/ground-104.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Ground 104</div><code className="hf-texture-example-class">hf-texture-ground-104</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">GROUND</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-ground-104</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/ground-068.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Ground 068</div><code className="hf-texture-example-class">hf-texture-ground-068</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">GROUND</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-ground-068</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/ground-080.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Ground 080</div><code className="hf-texture-example-class">hf-texture-ground-080</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">GROUND</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-ground-080</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/road-012-a.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Road 012 A</div><code className="hf-texture-example-class">hf-texture-road-012-a</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">ROAD</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-road-012-a</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/road-008-a.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Road 008 A</div><code className="hf-texture-example-class">hf-texture-road-008-a</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">ROAD</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-road-008-a</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/road-007.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Road 007</div><code className="hf-texture-example-class">hf-texture-road-007</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">ROAD</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-road-007</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/road-013-a.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Road 013 A</div><code className="hf-texture-example-class">hf-texture-road-013-a</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">ROAD</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-road-013-a</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/road-012-b.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Road 012 B</div><code className="hf-texture-example-class">hf-texture-road-012-b</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">ROAD</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-road-012-b</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/road-009-c.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Road 009 C</div><code className="hf-texture-example-class">hf-texture-road-009-c</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">ROAD</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-road-009-c</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/asphalt-031.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Asphalt 031</div><code className="hf-texture-example-class">hf-texture-asphalt-031</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">ASPHALT</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-asphalt-031</code></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="hf-texture-example-title">Wood</h3>
|
||||
<div className="hf-texture-example-grid">
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/wood.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Wood</div><code className="hf-texture-example-class">hf-texture-wood</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">WOOD</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-wood</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/wood-094.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Wood 094</div><code className="hf-texture-example-class">hf-texture-wood-094</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">WOOD</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-wood-094</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/wood-092.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Wood 092</div><code className="hf-texture-example-class">hf-texture-wood-092</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">WOOD</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-wood-092</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/wood-051.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Wood 051</div><code className="hf-texture-example-class">hf-texture-wood-051</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">WOOD</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-wood-051</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/wood-066.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Wood 066</div><code className="hf-texture-example-class">hf-texture-wood-066</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">WOOD</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-wood-066</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/wood-049.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Wood 049</div><code className="hf-texture-example-class">hf-texture-wood-049</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">WOOD</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-wood-049</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/wood-058.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Wood 058</div><code className="hf-texture-example-class">hf-texture-wood-058</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">WOOD</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-wood-058</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/wood-floor-051.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Wood Floor 051</div><code className="hf-texture-example-class">hf-texture-wood-floor-051</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">FLOOR</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-wood-floor-051</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/wood-floor-064.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Wood Floor 064</div><code className="hf-texture-example-class">hf-texture-wood-floor-064</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">FLOOR</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-wood-floor-064</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/wood-floor-070.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Wood Floor 070</div><code className="hf-texture-example-class">hf-texture-wood-floor-070</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">FLOOR</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-wood-floor-070</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/bark-014.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Bark 014</div><code className="hf-texture-example-class">hf-texture-bark-014</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">BARK</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-bark-014</code></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="hf-texture-example-title">Metal</h3>
|
||||
<div className="hf-texture-example-grid">
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/metal.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Metal</div><code className="hf-texture-example-class">hf-texture-metal</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">METAL</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-metal</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/metal-049-a.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Metal 049 A</div><code className="hf-texture-example-class">hf-texture-metal-049-a</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">METAL</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-metal-049-a</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/metal-055-a.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Metal 055 A</div><code className="hf-texture-example-class">hf-texture-metal-055-a</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">METAL</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-metal-055-a</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/metal-046-b.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Metal 046 B</div><code className="hf-texture-example-class">hf-texture-metal-046-b</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">METAL</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-metal-046-b</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/metal-061-b.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Metal 061 B</div><code className="hf-texture-example-class">hf-texture-metal-061-b</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">METAL</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-metal-061-b</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/metal-048-a.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Metal 048 A</div><code className="hf-texture-example-class">hf-texture-metal-048-a</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">METAL</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-metal-048-a</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/metal-032.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Metal 032</div><code className="hf-texture-example-class">hf-texture-metal-032</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">METAL</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-metal-032</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/metal-041-a.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Metal 041 A</div><code className="hf-texture-example-class">hf-texture-metal-041-a</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">METAL</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-metal-041-a</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/metal-038.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Metal 038</div><code className="hf-texture-example-class">hf-texture-metal-038</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">METAL</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-metal-038</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/diamond-plate-009.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Diamond Plate 009</div><code className="hf-texture-example-class">hf-texture-diamond-plate-009</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">PLATE</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-diamond-plate-009</code></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="hf-texture-example-title">Organic / Soft</h3>
|
||||
<div className="hf-texture-example-grid">
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/lava.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Lava</div><code className="hf-texture-example-class">hf-texture-lava</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">LAVA</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-lava</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/grass-005.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Grass 005</div><code className="hf-texture-example-class">hf-texture-grass-005</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">GRASS</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-grass-005</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/grass-001.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Grass 001</div><code className="hf-texture-example-class">hf-texture-grass-001</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">GRASS</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-grass-001</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/grass-004.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Grass 004</div><code className="hf-texture-example-class">hf-texture-grass-004</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">GRASS</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-grass-004</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/carpet.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Carpet</div><code className="hf-texture-example-class">hf-texture-carpet</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">WOVEN</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-carpet</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/fabric-083.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Fabric 083</div><code className="hf-texture-example-class">hf-texture-fabric-083</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">FABRIC</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-fabric-083</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/snow.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Snow</div><code className="hf-texture-example-class">hf-texture-snow</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">SNOW</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-snow</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/snow-015.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Snow 015</div><code className="hf-texture-example-class">hf-texture-snow-015</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">SNOW</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-snow-015</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/leather-037.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Leather 037</div><code className="hf-texture-example-class">hf-texture-leather-037</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">LEATHER</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-leather-037</code></div>
|
||||
</div>
|
||||
<div className="hf-texture-example-card" style={{ "--mask-url": "url('https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text/masks/fabric-080.png')" }}>
|
||||
<div className="hf-texture-example-meta"><div className="hf-texture-example-label">Fabric 080</div><code className="hf-texture-example-class">hf-texture-fabric-080</code></div>
|
||||
<div className="hf-texture-example-shadow"><div className="hf-texture-example-word">FABRIC</div></div>
|
||||
<div className="hf-texture-example-usage">Use <code>hf-texture-text hf-texture-fabric-080</code></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## Usage
|
||||
|
||||
After `npx hyperframes add texture-mask-text`, the installed snippet lives at `compositions/components/texture-mask-text/texture-mask-text.html` inside your current HyperFrames project. Open that file and paste the real `<style>` element near the bottom into your composition once; it defines `hf-texture-text` and every `hf-texture-*` class used by the examples above. Keep the installed texture PNGs in `assets/texture-mask-text/masks/`; the CSS references them with project-root URLs.
|
||||
@@ -147,3 +147,266 @@ a:not([class]) {
|
||||
a:not([class]):hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
/* Catalog texture examples */
|
||||
|
||||
.hf-texture-preview-panel {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
margin: 24px 0 40px;
|
||||
}
|
||||
|
||||
.hf-texture-preview-card {
|
||||
min-width: 0;
|
||||
border: 1px solid rgba(127, 127, 127, 0.22);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
background: #101010;
|
||||
}
|
||||
|
||||
.hf-texture-preview-label {
|
||||
margin-bottom: 12px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 12px;
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.hf-texture-preview-shadow {
|
||||
filter: drop-shadow(1px 2px 1px rgba(0, 0, 0, 0.48));
|
||||
}
|
||||
|
||||
.hf-texture-preview-word {
|
||||
color: #fff;
|
||||
font-family: Impact, 'Arial Black', sans-serif;
|
||||
font-size: 42px;
|
||||
line-height: 0.9;
|
||||
letter-spacing: 0;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
white-space: nowrap;
|
||||
-webkit-mask-image: var(--mask-url);
|
||||
mask-image: var(--mask-url);
|
||||
-webkit-mask-size: cover;
|
||||
mask-size: cover;
|
||||
-webkit-mask-position: center;
|
||||
mask-position: center;
|
||||
-webkit-mask-mode: luminance;
|
||||
mask-mode: luminance;
|
||||
}
|
||||
|
||||
.hf-texture-animate-demo {
|
||||
border: 1px solid rgba(127, 127, 127, 0.22);
|
||||
border-radius: 8px;
|
||||
padding: 22px;
|
||||
margin: 20px 0 24px;
|
||||
background:
|
||||
linear-gradient(90deg, rgba(255, 255, 255, 0.54) 1px, transparent 1px),
|
||||
#f1f1f1;
|
||||
background-size: 44px 100%;
|
||||
}
|
||||
|
||||
.hf-texture-animate-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.hf-texture-animate-label {
|
||||
min-width: 0;
|
||||
color: rgba(24, 24, 24, 0.62);
|
||||
font-size: 13px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.hf-texture-animate-class {
|
||||
max-width: 62%;
|
||||
padding: 2px 6px;
|
||||
border: 1px solid rgba(24, 24, 24, 0.12);
|
||||
border-radius: 6px;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
color: rgba(24, 24, 24, 0.78);
|
||||
font-size: 11px;
|
||||
line-height: 1.3;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.hf-texture-animate-shadow {
|
||||
filter: drop-shadow(1px 2px 1px rgba(0, 0, 0, 0.48));
|
||||
}
|
||||
|
||||
.hf-texture-animate-word {
|
||||
color: #181818;
|
||||
font-family: Impact, 'Arial Black', sans-serif;
|
||||
font-size: 92px;
|
||||
line-height: 0.9;
|
||||
letter-spacing: 0;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
white-space: nowrap;
|
||||
-webkit-mask-image: var(--mask-url);
|
||||
mask-image: var(--mask-url);
|
||||
-webkit-mask-size: 180% 180%;
|
||||
mask-size: 180% 180%;
|
||||
-webkit-mask-position: 0% 50%;
|
||||
mask-position: 0% 50%;
|
||||
-webkit-mask-mode: luminance;
|
||||
mask-mode: luminance;
|
||||
animation: hf-texture-mask-pan 2.1s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
@keyframes hf-texture-mask-pan {
|
||||
from {
|
||||
-webkit-mask-position: 0% 50%;
|
||||
mask-position: 0% 50%;
|
||||
}
|
||||
|
||||
to {
|
||||
-webkit-mask-position: 100% 50%;
|
||||
mask-position: 100% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.hf-texture-example-groups {
|
||||
display: grid;
|
||||
gap: 32px;
|
||||
margin: 20px 0 40px;
|
||||
}
|
||||
|
||||
.hf-texture-example-title {
|
||||
margin: 0 0 10px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.hf-texture-example-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(min(100%, 320px), 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.hf-texture-example-card {
|
||||
border: 1px solid rgba(127, 127, 127, 0.22);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
background: #f1f1f1;
|
||||
}
|
||||
|
||||
.hf-texture-example-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.hf-texture-example-label {
|
||||
min-width: 0;
|
||||
color: rgba(24, 24, 24, 0.62);
|
||||
font-size: 13px;
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.hf-texture-example-class {
|
||||
max-width: 62%;
|
||||
padding: 2px 6px;
|
||||
border: 1px solid rgba(24, 24, 24, 0.12);
|
||||
border-radius: 6px;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
color: rgba(24, 24, 24, 0.78);
|
||||
font-size: 11px;
|
||||
line-height: 1.3;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.hf-texture-example-shadow {
|
||||
filter: drop-shadow(1px 2px 1px rgba(0, 0, 0, 0.48));
|
||||
}
|
||||
|
||||
.hf-texture-example-word {
|
||||
color: #181818;
|
||||
font-family: Impact, 'Arial Black', sans-serif;
|
||||
font-size: 54px;
|
||||
line-height: 0.9;
|
||||
letter-spacing: 0;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
white-space: nowrap;
|
||||
-webkit-mask-image: var(--mask-url);
|
||||
mask-image: var(--mask-url);
|
||||
-webkit-mask-size: cover;
|
||||
mask-size: cover;
|
||||
-webkit-mask-position: center;
|
||||
mask-position: center;
|
||||
-webkit-mask-mode: luminance;
|
||||
mask-mode: luminance;
|
||||
}
|
||||
|
||||
.hf-texture-example-usage {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
margin-top: 14px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid rgba(24, 24, 24, 0.1);
|
||||
color: rgba(24, 24, 24, 0.56);
|
||||
font-size: 12px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.hf-texture-example-usage code {
|
||||
min-width: 0;
|
||||
color: rgba(24, 24, 24, 0.82);
|
||||
font-size: 11px;
|
||||
line-height: 1.3;
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
@media (max-width: 860px) {
|
||||
.hf-texture-preview-panel {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 520px) {
|
||||
.hf-texture-preview-panel {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.hf-texture-preview-word {
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
.hf-texture-animate-meta {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.hf-texture-animate-class {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.hf-texture-animate-word {
|
||||
font-size: 56px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.hf-texture-animate-word {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,7 +176,8 @@
|
||||
"pages": [
|
||||
"catalog/components/grain-overlay",
|
||||
"catalog/components/grid-pixelate-wipe",
|
||||
"catalog/components/shimmer-sweep"
|
||||
"catalog/components/shimmer-sweep",
|
||||
"catalog/components/texture-mask-text"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -348,6 +348,20 @@
|
||||
"href": "/catalog/blocks/swirl-vortex",
|
||||
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/swirl-vortex.png"
|
||||
},
|
||||
{
|
||||
"name": "texture-mask-text",
|
||||
"type": "component",
|
||||
"title": "Texture Mask Text",
|
||||
"description": "CSS luminance masks that cut holes through letterforms - 66 pre-built texture masks from ambientCG PBR color maps",
|
||||
"tags": [
|
||||
"text",
|
||||
"texture",
|
||||
"mask",
|
||||
"effect"
|
||||
],
|
||||
"href": "/catalog/components/texture-mask-text",
|
||||
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/texture-mask-text.png"
|
||||
},
|
||||
{
|
||||
"name": "thermal-distortion",
|
||||
"type": "block",
|
||||
|
||||
@@ -52,6 +52,11 @@ const COMPONENT_ITEM: RegistryItem = {
|
||||
target: "compositions/components/my-component/my-component.css",
|
||||
type: "hyperframes:style",
|
||||
},
|
||||
{
|
||||
path: "assets/mask.png",
|
||||
target: "assets/my-component/mask.png",
|
||||
type: "hyperframes:asset",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -190,7 +195,7 @@ describe("runAdd (integration, mocked registry)", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("remaps component targets per hyperframes.json paths.components", async () => {
|
||||
it("remaps component snippet/style targets while leaving asset targets stable", async () => {
|
||||
const dir = tmp();
|
||||
try {
|
||||
const baseUrl = uniqueBase();
|
||||
@@ -206,9 +211,10 @@ describe("runAdd (integration, mocked registry)", () => {
|
||||
projectDir: dir,
|
||||
skipClipboard: true,
|
||||
});
|
||||
expect(result.written.length).toBe(2);
|
||||
expect(result.written.length).toBe(3);
|
||||
expect(existsSync(join(dir, "src/fx/my-component/my-component.html"))).toBe(true);
|
||||
expect(existsSync(join(dir, "src/fx/my-component/my-component.css"))).toBe(true);
|
||||
expect(existsSync(join(dir, "assets/my-component/mask.png"))).toBe(true);
|
||||
expect(result.snippet).toContain("src/fx/my-component/my-component.html");
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
|
||||
@@ -82,7 +82,7 @@ describe("renderLocal browser GPU config", () => {
|
||||
browserGpuMode: "software",
|
||||
resolved: true,
|
||||
});
|
||||
});
|
||||
}, 15_000);
|
||||
|
||||
it("forwards browserGpuMode='auto' into producer config (probe-then-choose)", async () => {
|
||||
await renderLocal("/tmp/project", "/tmp/out.mp4", {
|
||||
|
||||
@@ -391,6 +391,110 @@ describe("audio_src_not_found", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("texture_mask_asset_not_found", () => {
|
||||
it("errors when CSS mask-image references a missing local texture", () => {
|
||||
const html = `<html><body>
|
||||
<div data-composition-id="main" data-width="1920" data-height="1080">
|
||||
<div class="hf-texture-text hf-texture-lava">TEXT</div>
|
||||
</div>
|
||||
<style>
|
||||
.hf-texture-lava {
|
||||
-webkit-mask-image: url("masks/lava.png");
|
||||
mask-image: url("masks/lava.png");
|
||||
}
|
||||
</style>
|
||||
<script>window.__timelines = window.__timelines || {}; window.__timelines["main"] = gsap.timeline({ paused: true });</script>
|
||||
</body></html>`;
|
||||
const project = makeProject(html);
|
||||
|
||||
const { totalErrors, results } = lintProject(project);
|
||||
const finding = results[0]?.result.findings.find(
|
||||
(item) => item.code === "texture_mask_asset_not_found",
|
||||
);
|
||||
|
||||
expect(totalErrors).toBeGreaterThan(0);
|
||||
expect(finding).toBeDefined();
|
||||
expect(finding?.severity).toBe("error");
|
||||
expect(finding?.message).toContain("masks/lava.png");
|
||||
});
|
||||
|
||||
it("does not error when the referenced texture mask exists", () => {
|
||||
const html = `<html><body>
|
||||
<div data-composition-id="main" data-width="1920" data-height="1080">
|
||||
<div class="hf-texture-text hf-texture-lava">TEXT</div>
|
||||
</div>
|
||||
<style>
|
||||
.hf-texture-lava {
|
||||
-webkit-mask-image: url("masks/lava.png");
|
||||
mask-image: url("masks/lava.png");
|
||||
}
|
||||
</style>
|
||||
<script>window.__timelines = window.__timelines || {}; window.__timelines["main"] = gsap.timeline({ paused: true });</script>
|
||||
</body></html>`;
|
||||
const project = makeProject(html);
|
||||
mkdirSync(join(project.dir, "masks"), { recursive: true });
|
||||
writeFileSync(join(project.dir, "masks", "lava.png"), "fake");
|
||||
|
||||
const { results } = lintProject(project);
|
||||
const finding = results[0]?.result.findings.find(
|
||||
(item) => item.code === "texture_mask_asset_not_found",
|
||||
);
|
||||
|
||||
expect(finding).toBeUndefined();
|
||||
});
|
||||
|
||||
it("resolves mask-image URLs inside linked sub-composition stylesheets", () => {
|
||||
const project = makeProject(validHtml(), {
|
||||
"scene.html": `<html><head><link rel="stylesheet" href="scene.css"></head><body>
|
||||
<div data-composition-id="scene" data-width="1920" data-height="1080">
|
||||
<div class="hf-texture-text hf-texture-lava">TEXT</div>
|
||||
</div>
|
||||
<script>window.__timelines = window.__timelines || {}; window.__timelines["scene"] = gsap.timeline({ paused: true });</script>
|
||||
</body></html>`,
|
||||
});
|
||||
writeFileSync(
|
||||
join(project.dir, "compositions", "scene.css"),
|
||||
'.hf-texture-lava { mask-image: url("masks/lava.png"); }',
|
||||
);
|
||||
mkdirSync(join(project.dir, "compositions", "masks"), { recursive: true });
|
||||
writeFileSync(join(project.dir, "compositions", "masks", "lava.png"), "fake");
|
||||
|
||||
const { results } = lintProject(project);
|
||||
const finding = results[0]?.result.findings.find(
|
||||
(item) => item.code === "texture_mask_asset_not_found",
|
||||
);
|
||||
|
||||
expect(finding).toBeUndefined();
|
||||
});
|
||||
|
||||
it("resolves root-absolute mask-image URLs from the project root", () => {
|
||||
const html = `<html><body>
|
||||
<div data-composition-id="main" data-width="1920" data-height="1080">
|
||||
<div class="hf-texture-text hf-texture-lava">TEXT</div>
|
||||
</div>
|
||||
<style>
|
||||
.hf-texture-lava {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/lava.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/lava.png");
|
||||
}
|
||||
</style>
|
||||
<script>window.__timelines = window.__timelines || {}; window.__timelines["main"] = gsap.timeline({ paused: true });</script>
|
||||
</body></html>`;
|
||||
const project = makeProject(html);
|
||||
mkdirSync(join(project.dir, "assets", "texture-mask-text", "masks"), {
|
||||
recursive: true,
|
||||
});
|
||||
writeFileSync(join(project.dir, "assets", "texture-mask-text", "masks", "lava.png"), "fake");
|
||||
|
||||
const { results } = lintProject(project);
|
||||
const finding = results[0]?.result.findings.find(
|
||||
(item) => item.code === "texture_mask_asset_not_found",
|
||||
);
|
||||
|
||||
expect(finding).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("multiple_root_compositions", () => {
|
||||
it("fires when two HTML files have data-composition-id", () => {
|
||||
const project = makeProject(validHtml());
|
||||
|
||||
@@ -18,6 +18,12 @@ interface HtmlSource {
|
||||
compSrcPath?: string;
|
||||
}
|
||||
|
||||
interface CssSource {
|
||||
content: string;
|
||||
/** Root-relative path to the CSS file. Undefined means inline HTML CSS. */
|
||||
rootRelativePath?: string;
|
||||
}
|
||||
|
||||
export interface ProjectLintResult {
|
||||
results: Array<{ file: string; result: HyperframeLintResult }>;
|
||||
totalErrors: number;
|
||||
@@ -26,6 +32,16 @@ export interface ProjectLintResult {
|
||||
}
|
||||
|
||||
const AUDIO_EXTENSIONS = new Set([".mp3", ".wav", ".aac", ".ogg", ".m4a", ".flac", ".opus"]);
|
||||
const STYLE_BLOCK_RE = /<style\b[^>]*>([\s\S]*?)<\/style>/gi;
|
||||
const OPEN_TAG_RE = /<([a-z][\w:-]*)(\s[^<>]*?)?>/gi;
|
||||
const MASK_IMAGE_URL_RE =
|
||||
/\b(?:-webkit-)?mask-image\s*:\s*[^;{}]*url\(\s*(?:"([^"]+)"|'([^']+)'|([^"')\s]+))\s*\)/gi;
|
||||
|
||||
function readHtmlAttr(tag: string, name: string): string | null {
|
||||
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
const match = tag.match(new RegExp(`\\b${escaped}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`, "i"));
|
||||
return match?.[1] ?? match?.[2] ?? null;
|
||||
}
|
||||
|
||||
function isLocalStylesheetHref(href: string): boolean {
|
||||
return !!href && !/^(https?:|data:|blob:|\/\/)/i.test(href);
|
||||
@@ -53,6 +69,62 @@ function collectExternalStyles(
|
||||
return styles;
|
||||
}
|
||||
|
||||
function collectCssSources(projectDir: string, html: string, compSrcPath?: string): CssSource[] {
|
||||
const sources: CssSource[] = [];
|
||||
|
||||
let styleMatch: RegExpExecArray | null;
|
||||
const stylePattern = new RegExp(STYLE_BLOCK_RE.source, STYLE_BLOCK_RE.flags);
|
||||
while ((styleMatch = stylePattern.exec(html)) !== null) {
|
||||
sources.push({ content: styleMatch[1] ?? "" });
|
||||
}
|
||||
|
||||
const linkRe = /<link\b[^>]*>/gi;
|
||||
let linkMatch: RegExpExecArray | null;
|
||||
while ((linkMatch = linkRe.exec(html)) !== null) {
|
||||
const tag = linkMatch[0];
|
||||
const rel = readHtmlAttr(tag, "rel") ?? "";
|
||||
if (!rel.split(/\s+/).some((part) => part.toLowerCase() === "stylesheet")) continue;
|
||||
const href = readHtmlAttr(tag, "href") ?? "";
|
||||
if (!isLocalStylesheetHref(href)) continue;
|
||||
|
||||
const rootRelativePath = compSrcPath ? join(dirname(compSrcPath), href) : href;
|
||||
const resolved = resolve(projectDir, rootRelativePath);
|
||||
if (!existsSync(resolved)) continue;
|
||||
sources.push({ content: readFileSync(resolved, "utf-8"), rootRelativePath });
|
||||
}
|
||||
|
||||
let tagMatch: RegExpExecArray | null;
|
||||
const tagPattern = new RegExp(OPEN_TAG_RE.source, OPEN_TAG_RE.flags);
|
||||
while ((tagMatch = tagPattern.exec(html)) !== null) {
|
||||
const tag = tagMatch[0];
|
||||
const style = readHtmlAttr(tag, "style");
|
||||
if (!style) continue;
|
||||
sources.push({ content: style });
|
||||
}
|
||||
|
||||
return sources;
|
||||
}
|
||||
|
||||
function isRemoteOrInlineUrl(url: string): boolean {
|
||||
return /^(https?:|data:|blob:|\/\/|#)/i.test(url);
|
||||
}
|
||||
|
||||
function cleanAssetUrl(url: string): string {
|
||||
return url.trim().split(/[?#]/, 1)[0] ?? "";
|
||||
}
|
||||
|
||||
function resolveCssAssetPath(
|
||||
projectDir: string,
|
||||
url: string,
|
||||
htmlCompSrcPath?: string,
|
||||
cssRootRelativePath?: string,
|
||||
): string {
|
||||
if (url.startsWith("/")) return resolve(projectDir, url.slice(1));
|
||||
if (cssRootRelativePath) return resolve(projectDir, join(dirname(cssRootRelativePath), url));
|
||||
if (htmlCompSrcPath) return resolve(projectDir, rewriteAssetPath(htmlCompSrcPath, url));
|
||||
return resolve(projectDir, url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lint the root index.html and all sub-compositions in the compositions/ directory.
|
||||
* Returns aggregated results across all files.
|
||||
@@ -101,6 +173,7 @@ export function lintProject(project: ProjectDir): ProjectLintResult {
|
||||
const projectFindings = [
|
||||
...lintProjectAudioFiles(project.dir, allHtmlSources),
|
||||
...lintAudioSrcNotFound(project.dir, allHtmlSources),
|
||||
...lintTextureMaskAssetNotFound(project.dir, allHtmlSources),
|
||||
...lintMultipleRootCompositions(project.dir),
|
||||
...lintDuplicateAudioTracks(allHtmlSources),
|
||||
];
|
||||
@@ -215,6 +288,49 @@ function lintAudioSrcNotFound(
|
||||
return findings;
|
||||
}
|
||||
|
||||
function lintTextureMaskAssetNotFound(
|
||||
projectDir: string,
|
||||
htmlSources: HtmlSource[],
|
||||
): HyperframeLintFinding[] {
|
||||
const missing = new Map<string, string>();
|
||||
|
||||
for (const { html, compSrcPath } of htmlSources) {
|
||||
for (const cssSource of collectCssSources(projectDir, html, compSrcPath)) {
|
||||
let match: RegExpExecArray | null;
|
||||
const pattern = new RegExp(MASK_IMAGE_URL_RE.source, MASK_IMAGE_URL_RE.flags);
|
||||
while ((match = pattern.exec(cssSource.content)) !== null) {
|
||||
const rawUrl = match[1] ?? match[2] ?? match[3] ?? "";
|
||||
const url = cleanAssetUrl(rawUrl);
|
||||
if (!url || isRemoteOrInlineUrl(url)) continue;
|
||||
if (/^__[A-Z_]+__$/.test(url)) continue;
|
||||
|
||||
const resolved = resolveCssAssetPath(
|
||||
projectDir,
|
||||
url,
|
||||
compSrcPath,
|
||||
cssSource.rootRelativePath,
|
||||
);
|
||||
if (existsSync(resolved)) continue;
|
||||
missing.set(url, resolved);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (missing.size === 0) return [];
|
||||
const urls = [...missing.keys()];
|
||||
return [
|
||||
{
|
||||
code: "texture_mask_asset_not_found",
|
||||
severity: "error",
|
||||
message: `CSS mask-image references file(s) not found in the project: ${urls.join(", ")}.`,
|
||||
fixHint:
|
||||
urls.length === 1
|
||||
? `Add "${urls[0]}" to the project, or update the mask-image URL to point to an existing texture mask.`
|
||||
: "Add the missing texture mask files to the project, or update the mask-image URLs to point to existing files.",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Error if multiple root-level HTML files with data-composition-id exist.
|
||||
* Scans the project directory filesystem (not just what lintProject chose to read)
|
||||
|
||||
@@ -7,6 +7,7 @@ import { gsapRules } from "./rules/gsap";
|
||||
import { captionRules } from "./rules/captions";
|
||||
import { compositionRules } from "./rules/composition";
|
||||
import { adapterRules } from "./rules/adapters";
|
||||
import { textureRules } from "./rules/textures";
|
||||
|
||||
const ALL_RULES = [
|
||||
...coreRules,
|
||||
@@ -15,6 +16,7 @@ const ALL_RULES = [
|
||||
...captionRules,
|
||||
...compositionRules,
|
||||
...adapterRules,
|
||||
...textureRules,
|
||||
];
|
||||
|
||||
export function lintHyperframeHtml(
|
||||
|
||||
@@ -35,6 +35,21 @@ describe("composition rules", () => {
|
||||
expect(finding).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not count inline style block internals as structural lines", () => {
|
||||
const style = `<style>\n${Array.from({ length: 320 }, (_, i) => `.rule-${i} { color: red; }`).join("\n")}\n</style>`;
|
||||
const html = `<!doctype html>
|
||||
<html>
|
||||
<head>${style}</head>
|
||||
<body>
|
||||
<div data-composition-id="main" data-start="0" data-duration="1">TEXT</div>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
const result = lintHyperframeHtml(html, { filePath: "/project/index.html" });
|
||||
const finding = result.findings.find((f) => f.code === "composition_file_too_large");
|
||||
expect(finding).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not warn for large registry source block files", () => {
|
||||
const html = Array.from({ length: 301 }, (_, i) =>
|
||||
i === 0 ? "<html><body>" : `<!-- filler ${i} -->`,
|
||||
|
||||
@@ -16,6 +16,10 @@ function countPhysicalLines(source: string): number {
|
||||
return withoutFinalNewline.split("\n").length;
|
||||
}
|
||||
|
||||
function countStructuralLines(source: string): number {
|
||||
return countPhysicalLines(source.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, "<style></style>"));
|
||||
}
|
||||
|
||||
function isRegistrySourceFile(filePath?: string): boolean {
|
||||
if (!filePath) return false;
|
||||
|
||||
@@ -38,7 +42,7 @@ export const compositionRules: Array<(ctx: LintContext) => HyperframeLintFinding
|
||||
({ rawSource, options }) => {
|
||||
if (isRegistrySourceFile(options.filePath) || isRegistryInstalledFile(rawSource)) return [];
|
||||
|
||||
const lineCount = countPhysicalLines(rawSource);
|
||||
const lineCount = countStructuralLines(rawSource);
|
||||
if (lineCount <= MAX_COMPOSITION_LINES) return [];
|
||||
|
||||
const splitTarget = options.isSubComposition
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { lintHyperframeHtml } from "../hyperframeLinter.js";
|
||||
|
||||
function baseHtml(body: string, style = ""): string {
|
||||
return `<html><body>
|
||||
<div data-composition-id="main" data-width="1920" data-height="1080">
|
||||
${body}
|
||||
</div>
|
||||
<style>${style}</style>
|
||||
<script>window.__timelines = window.__timelines || {}; window.__timelines["main"] = gsap.timeline({ paused: true });</script>
|
||||
</body></html>`;
|
||||
}
|
||||
|
||||
const textureCss = `
|
||||
.hf-texture-text {
|
||||
color: #fff;
|
||||
-webkit-mask-size: var(--mask-size, cover);
|
||||
mask-size: var(--mask-size, cover);
|
||||
}
|
||||
.hf-texture-lava {
|
||||
-webkit-mask-image: url("masks/lava.png");
|
||||
mask-image: url("masks/lava.png");
|
||||
}
|
||||
`;
|
||||
|
||||
describe("texture rules", () => {
|
||||
it("does not warn for a valid texture mask text usage", () => {
|
||||
const html = baseHtml(
|
||||
'<div class="shadow"><div class="hf-texture-text hf-texture-lava">TEXT</div></div>',
|
||||
`${textureCss}.shadow { filter: drop-shadow(1px 2px 1px rgba(0,0,0,.48)); }`,
|
||||
);
|
||||
|
||||
const result = lintHyperframeHtml(html);
|
||||
|
||||
expect(result.findings.filter((finding) => finding.code.startsWith("texture_"))).toEqual([]);
|
||||
});
|
||||
|
||||
it("warns when a material class is used without hf-texture-text", () => {
|
||||
const html = baseHtml('<div class="hf-texture-lava">TEXT</div>', textureCss);
|
||||
|
||||
const result = lintHyperframeHtml(html);
|
||||
const finding = result.findings.find((item) => item.code === "texture_class_missing_base");
|
||||
|
||||
expect(finding).toBeDefined();
|
||||
expect(finding?.severity).toBe("warning");
|
||||
expect(finding?.fixHint).toContain("hf-texture-text");
|
||||
});
|
||||
|
||||
it("warns when hf-texture-text has no material class or custom mask image", () => {
|
||||
const html = baseHtml('<div class="hf-texture-text">TEXT</div>', textureCss);
|
||||
|
||||
const result = lintHyperframeHtml(html);
|
||||
const finding = result.findings.find((item) => item.code === "texture_text_missing_mask");
|
||||
|
||||
expect(finding).toBeDefined();
|
||||
expect(finding?.severity).toBe("warning");
|
||||
});
|
||||
|
||||
it("allows hf-texture-text with an inline custom mask image", () => {
|
||||
const html = baseHtml(
|
||||
'<div class="hf-texture-text" style="-webkit-mask-image:url(custom.png); mask-image:url(custom.png)">TEXT</div>',
|
||||
textureCss,
|
||||
);
|
||||
|
||||
const result = lintHyperframeHtml(html);
|
||||
const finding = result.findings.find((item) => item.code === "texture_text_missing_mask");
|
||||
|
||||
expect(finding).toBeUndefined();
|
||||
});
|
||||
|
||||
it("warns when a texture material class is not defined by local CSS", () => {
|
||||
const html = baseHtml('<div class="hf-texture-text hf-texture-marbel">TEXT</div>', textureCss);
|
||||
|
||||
const result = lintHyperframeHtml(html);
|
||||
const finding = result.findings.find((item) => item.code === "texture_class_unknown");
|
||||
|
||||
expect(finding).toBeDefined();
|
||||
expect(finding?.message).toContain("hf-texture-marbel");
|
||||
});
|
||||
|
||||
it("warns when drop-shadow is applied inline to the textured text element", () => {
|
||||
const html = baseHtml(
|
||||
'<div class="hf-texture-text hf-texture-lava" style="filter: drop-shadow(1px 2px 1px black)">TEXT</div>',
|
||||
textureCss,
|
||||
);
|
||||
|
||||
const result = lintHyperframeHtml(html);
|
||||
const finding = result.findings.find((item) => item.code === "texture_drop_shadow_on_text");
|
||||
|
||||
expect(finding).toBeDefined();
|
||||
expect(finding?.fixHint).toContain("wrapper");
|
||||
});
|
||||
|
||||
it("warns when drop-shadow is applied by CSS directly to hf-texture-text", () => {
|
||||
const html = baseHtml(
|
||||
'<div class="hf-texture-text hf-texture-lava">TEXT</div>',
|
||||
`${textureCss}.hf-texture-text { filter: drop-shadow(1px 2px 1px black); }`,
|
||||
);
|
||||
|
||||
const result = lintHyperframeHtml(html);
|
||||
const finding = result.findings.find((item) => item.code === "texture_drop_shadow_on_text");
|
||||
|
||||
expect(finding).toBeDefined();
|
||||
expect(finding?.selector).toBe(".hf-texture-text");
|
||||
});
|
||||
|
||||
it("warns when drop-shadow targets a material class before the mask rule is declared", () => {
|
||||
const html = baseHtml(
|
||||
'<div class="hf-texture-text hf-texture-lava">TEXT</div>',
|
||||
`.hf-texture-lava { filter: drop-shadow(1px 2px 1px black); }
|
||||
${textureCss}`,
|
||||
);
|
||||
|
||||
const result = lintHyperframeHtml(html);
|
||||
const finding = result.findings.find((item) => item.code === "texture_drop_shadow_on_text");
|
||||
|
||||
expect(finding).toBeDefined();
|
||||
expect(finding?.selector).toBe(".hf-texture-lava");
|
||||
});
|
||||
|
||||
it("warns when drop-shadow targets another class on the textured text element", () => {
|
||||
const html = baseHtml(
|
||||
'<div class="hf-texture-text hf-texture-lava headline">TEXT</div>',
|
||||
`${textureCss}.headline { filter: drop-shadow(1px 2px 1px black); }`,
|
||||
);
|
||||
|
||||
const result = lintHyperframeHtml(html);
|
||||
const finding = result.findings.find((item) => item.code === "texture_drop_shadow_on_text");
|
||||
|
||||
expect(finding).toBeDefined();
|
||||
expect(finding?.selector).toBe(".headline");
|
||||
});
|
||||
|
||||
it("does not warn when another-class drop-shadow selector needs an unmatched ancestor", () => {
|
||||
const html = baseHtml(
|
||||
'<div class="hf-texture-text hf-texture-lava headline">TEXT</div>',
|
||||
`${textureCss}.card .headline { filter: drop-shadow(1px 2px 1px black); }`,
|
||||
);
|
||||
|
||||
const result = lintHyperframeHtml(html);
|
||||
const finding = result.findings.find((item) => item.code === "texture_drop_shadow_on_text");
|
||||
|
||||
expect(finding).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,223 @@
|
||||
import postcss from "postcss";
|
||||
import type { LintContext, HyperframeLintFinding, OpenTag } from "../context";
|
||||
import { readAttr, truncateSnippet } from "../utils";
|
||||
|
||||
const TEXTURE_BASE_CLASS = "hf-texture-text";
|
||||
const TEXTURE_CLASS_PREFIX = "hf-texture-";
|
||||
|
||||
type DropShadowRule = {
|
||||
selector: string;
|
||||
directlyTargetsTexture: boolean;
|
||||
};
|
||||
|
||||
function classNames(tag: OpenTag): string[] {
|
||||
return (readAttr(tag.raw, "class") ?? "").split(/\s+/).filter(Boolean);
|
||||
}
|
||||
|
||||
function isTextureMaterialClass(className: string): boolean {
|
||||
return className.startsWith(TEXTURE_CLASS_PREFIX) && className !== TEXTURE_BASE_CLASS;
|
||||
}
|
||||
|
||||
function hasInlineMaskImage(tag: OpenTag): boolean {
|
||||
const style = readAttr(tag.raw, "style") ?? "";
|
||||
return /\b(?:-webkit-)?mask-image\s*:/i.test(style);
|
||||
}
|
||||
|
||||
function hasInlineDropShadow(tag: OpenTag): boolean {
|
||||
const style = readAttr(tag.raw, "style") ?? "";
|
||||
return /\bfilter\s*:\s*[^;]*\bdrop-shadow\s*\(/i.test(style);
|
||||
}
|
||||
|
||||
function classNamesInSelector(selector: string): string[] {
|
||||
const classes = new Set<string>();
|
||||
const pattern = /\.([A-Za-z_][\w-]*)/g;
|
||||
let match: RegExpExecArray | null;
|
||||
while ((match = pattern.exec(selector)) !== null) {
|
||||
const className = match[1];
|
||||
if (!className) continue;
|
||||
classes.add(className);
|
||||
}
|
||||
return [...classes];
|
||||
}
|
||||
|
||||
function textureClassesInSelector(selector: string): string[] {
|
||||
return classNamesInSelector(selector).filter(isTextureMaterialClass);
|
||||
}
|
||||
|
||||
function simpleSelectorMatchesTag(selector: string, tag: OpenTag, tagClasses: string[]): boolean {
|
||||
const trimmed = selector.trim();
|
||||
const simpleSelectorPattern = /^(?:[A-Za-z][\w-]*)?(?:\.[A-Za-z_][\w-]*)+$/;
|
||||
if (!simpleSelectorPattern.test(trimmed)) return false;
|
||||
|
||||
const typeMatch = /^([A-Za-z][\w-]*)/.exec(trimmed);
|
||||
if (typeMatch && typeMatch[1]!.toLowerCase() !== tag.name) return false;
|
||||
|
||||
const selectorClasses = classNamesInSelector(trimmed);
|
||||
return (
|
||||
selectorClasses.length > 0 &&
|
||||
selectorClasses.every((className) => tagClasses.includes(className))
|
||||
);
|
||||
}
|
||||
|
||||
function collectTextureCss(styles: LintContext["styles"]): {
|
||||
definedTextureClasses: Set<string>;
|
||||
dropShadowRules: DropShadowRule[];
|
||||
} {
|
||||
const definedTextureClasses = new Set<string>();
|
||||
const dropShadowRules: DropShadowRule[] = [];
|
||||
const roots: postcss.Root[] = [];
|
||||
|
||||
for (const style of styles) {
|
||||
let root: postcss.Root;
|
||||
try {
|
||||
root = postcss.parse(style.content);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
roots.push(root);
|
||||
|
||||
root.walkRules((rule) => {
|
||||
const selectors = rule.selectors ?? [];
|
||||
let hasMaskImage = false;
|
||||
|
||||
for (const node of rule.nodes ?? []) {
|
||||
if (node.type !== "decl") continue;
|
||||
const prop = node.prop.toLowerCase();
|
||||
if (prop === "mask-image" || prop === "-webkit-mask-image") hasMaskImage = true;
|
||||
}
|
||||
|
||||
if (hasMaskImage) {
|
||||
for (const selector of selectors) {
|
||||
for (const className of textureClassesInSelector(selector)) {
|
||||
definedTextureClasses.add(className);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (const root of roots) {
|
||||
root.walkRules((rule) => {
|
||||
const selectors = rule.selectors ?? [];
|
||||
let hasDropShadow = false;
|
||||
|
||||
for (const node of rule.nodes ?? []) {
|
||||
if (node.type !== "decl") continue;
|
||||
if (node.prop.toLowerCase() === "filter" && /\bdrop-shadow\s*\(/i.test(node.value)) {
|
||||
hasDropShadow = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasDropShadow) {
|
||||
for (const selector of selectors) {
|
||||
const targetsBaseClass = /\.hf-texture-text\b/.test(selector);
|
||||
const targetsDefinedTextureClass = textureClassesInSelector(selector).some((className) =>
|
||||
definedTextureClasses.has(className),
|
||||
);
|
||||
dropShadowRules.push({
|
||||
selector,
|
||||
directlyTargetsTexture: targetsBaseClass || targetsDefinedTextureClass,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return { definedTextureClasses, dropShadowRules };
|
||||
}
|
||||
|
||||
export const textureRules: Array<(ctx: LintContext) => HyperframeLintFinding[]> = [
|
||||
({ tags, styles }) => {
|
||||
const findings: HyperframeLintFinding[] = [];
|
||||
const { definedTextureClasses, dropShadowRules } = collectTextureCss(styles);
|
||||
|
||||
for (const { selector, directlyTargetsTexture } of dropShadowRules) {
|
||||
if (!directlyTargetsTexture) continue;
|
||||
findings.push({
|
||||
code: "texture_drop_shadow_on_text",
|
||||
severity: "warning",
|
||||
message: "Drop shadow is applied directly to textured text.",
|
||||
selector,
|
||||
fixHint:
|
||||
"Wrap the textured text and apply `filter: drop-shadow(...)` to the wrapper, not the `hf-texture-text` element.",
|
||||
});
|
||||
}
|
||||
|
||||
for (const tag of tags) {
|
||||
if (tag.name === "style" || tag.name === "script") continue;
|
||||
|
||||
const classes = classNames(tag);
|
||||
if (classes.length === 0) continue;
|
||||
|
||||
const hasBaseClass = classes.includes(TEXTURE_BASE_CLASS);
|
||||
const textureClasses = classes.filter(isTextureMaterialClass);
|
||||
|
||||
if (textureClasses.length > 0 && !hasBaseClass) {
|
||||
findings.push({
|
||||
code: "texture_class_missing_base",
|
||||
severity: "warning",
|
||||
message: `Texture material class \`${textureClasses[0]}\` is used without \`${TEXTURE_BASE_CLASS}\`.`,
|
||||
elementId: readAttr(tag.raw, "id") || undefined,
|
||||
fixHint: `Add \`${TEXTURE_BASE_CLASS}\` alongside the material class, for example \`class="${TEXTURE_BASE_CLASS} ${textureClasses[0]}"\`.`,
|
||||
snippet: truncateSnippet(tag.raw),
|
||||
});
|
||||
}
|
||||
|
||||
if (hasBaseClass && textureClasses.length === 0 && !hasInlineMaskImage(tag)) {
|
||||
findings.push({
|
||||
code: "texture_text_missing_mask",
|
||||
severity: "warning",
|
||||
message: `\`${TEXTURE_BASE_CLASS}\` is used without a texture material class or custom mask image.`,
|
||||
elementId: readAttr(tag.raw, "id") || undefined,
|
||||
fixHint:
|
||||
"Add a material class such as `hf-texture-lava`, or set `mask-image` and `-webkit-mask-image` on the element.",
|
||||
snippet: truncateSnippet(tag.raw),
|
||||
});
|
||||
}
|
||||
|
||||
for (const textureClass of textureClasses) {
|
||||
if (definedTextureClasses.has(textureClass)) continue;
|
||||
findings.push({
|
||||
code: "texture_class_unknown",
|
||||
severity: "warning",
|
||||
message: `Texture material class \`${textureClass}\` is not defined by local CSS.`,
|
||||
elementId: readAttr(tag.raw, "id") || undefined,
|
||||
fixHint:
|
||||
"Paste the Texture Mask Text component `<style>...</style>` block into the composition, or fix the texture class typo.",
|
||||
snippet: truncateSnippet(tag.raw),
|
||||
});
|
||||
}
|
||||
|
||||
if (hasBaseClass) {
|
||||
for (const rule of dropShadowRules) {
|
||||
if (rule.directlyTargetsTexture) continue;
|
||||
if (!simpleSelectorMatchesTag(rule.selector, tag, classes)) continue;
|
||||
findings.push({
|
||||
code: "texture_drop_shadow_on_text",
|
||||
severity: "warning",
|
||||
message: "Drop shadow is applied directly to textured text.",
|
||||
selector: rule.selector,
|
||||
elementId: readAttr(tag.raw, "id") || undefined,
|
||||
fixHint:
|
||||
"Wrap the textured text and apply `filter: drop-shadow(...)` to the wrapper, not the `hf-texture-text` element.",
|
||||
snippet: truncateSnippet(tag.raw),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (hasBaseClass && hasInlineDropShadow(tag)) {
|
||||
findings.push({
|
||||
code: "texture_drop_shadow_on_text",
|
||||
severity: "warning",
|
||||
message: "Drop shadow is applied directly to textured text.",
|
||||
elementId: readAttr(tag.raw, "id") || undefined,
|
||||
fixHint:
|
||||
"Wrap the textured text and apply `filter: drop-shadow(...)` to the wrapper, not the `hf-texture-text` element.",
|
||||
snippet: truncateSnippet(tag.raw),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return findings;
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,18 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const here = dirname(fileURLToPath(import.meta.url));
|
||||
const generatorSource = readFileSync(
|
||||
resolve(here, "../../../../scripts/generate-catalog-pages.ts"),
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
describe("catalog generator texture instructions", () => {
|
||||
it("pins the unambiguous texture style-block copy instruction", () => {
|
||||
expect(generatorSource).toContain("paste the real <style> block");
|
||||
expect(generatorSource).toContain("near the bottom into the composition once");
|
||||
expect(generatorSource).toContain("real \\`<style>\\` element near the bottom");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,286 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1920, height=1080" />
|
||||
<title>Texture Mask Text - Demo</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
background: #111;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="demo"
|
||||
data-composition-id="texture-mask-text-demo"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
data-duration="5"
|
||||
data-start="0"
|
||||
>
|
||||
<div class="demo-canvas">
|
||||
<div class="header">
|
||||
<div class="title">Texture Mask Text</div>
|
||||
<div class="count">66 ambientCG luminance masks</div>
|
||||
</div>
|
||||
<div class="groups" id="texture-groups"></div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#demo {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
background: #111;
|
||||
color: #fff;
|
||||
font-family:
|
||||
Arial Black,
|
||||
Arial,
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
.hf-texture-text {
|
||||
color: #fff;
|
||||
-webkit-mask-size: var(--mask-size, cover);
|
||||
mask-size: var(--mask-size, cover);
|
||||
-webkit-mask-position: var(--mask-position, center);
|
||||
mask-position: var(--mask-position, center);
|
||||
-webkit-mask-mode: luminance;
|
||||
mask-mode: luminance;
|
||||
filter: contrast(var(--mask-contrast, 1));
|
||||
}
|
||||
|
||||
.demo-canvas {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
background: #111;
|
||||
padding: 42px 44px 38px;
|
||||
}
|
||||
|
||||
.header {
|
||||
height: 84px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.12);
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 34px;
|
||||
line-height: 1;
|
||||
letter-spacing: 0;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.count {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
letter-spacing: 0;
|
||||
color: rgba(255, 255, 255, 0.55);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.groups {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-rows: repeat(2, 1fr);
|
||||
gap: 22px 24px;
|
||||
width: 1832px;
|
||||
height: 898px;
|
||||
}
|
||||
|
||||
.group-panel {
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.group-header {
|
||||
height: 34px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.group-name {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 1;
|
||||
letter-spacing: 0;
|
||||
text-transform: uppercase;
|
||||
color: rgba(255, 255, 255, 0.74);
|
||||
}
|
||||
|
||||
.group-count {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 10px;
|
||||
line-height: 1;
|
||||
letter-spacing: 0;
|
||||
text-transform: uppercase;
|
||||
color: rgba(255, 255, 255, 0.52);
|
||||
}
|
||||
|
||||
.group-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 8px 10px;
|
||||
}
|
||||
|
||||
.cell {
|
||||
min-width: 0;
|
||||
height: 66px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 9px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.label {
|
||||
width: 100%;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 11px;
|
||||
line-height: 1.1;
|
||||
letter-spacing: 0;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.shadow-wrap {
|
||||
width: 100%;
|
||||
filter: drop-shadow(2px 3px 4px rgba(0, 0, 0, 0.46));
|
||||
}
|
||||
|
||||
.sample {
|
||||
width: 100%;
|
||||
font-family:
|
||||
Impact,
|
||||
Arial Black,
|
||||
Arial,
|
||||
sans-serif;
|
||||
font-size: 31px;
|
||||
line-height: 0.9;
|
||||
letter-spacing: 0;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
const textureMaskTextGroups = [
|
||||
[
|
||||
"Masonry",
|
||||
"brick|Brick|MORTAR,bricks-104|Bricks 104|BRICK,bricks-102|Bricks 102|BRICK,bricks-101|Bricks 101|BRICK,bricks-075-a|Bricks 075 A|BRICK,concrete|Concrete|SCORED,concrete-034|Concrete 034|CONCRETE,concrete-047-a|Concrete 047 A|CONCRETE,concrete-046|Concrete 046|CONCRETE,concrete-042-a|Concrete 042 A|CONCRETE,plaster-001|Plaster 001|PLASTER,painted-plaster-017|Painted Plaster 017|PLASTER",
|
||||
],
|
||||
[
|
||||
"Stone",
|
||||
"rock|Rock|RUINED,rock-063|Rock 063|ROCK,rock-058|Rock 058|ROCK,onyx|Onyx|VEINED,marble-012|Marble 012|MARBLE,marble-016|Marble 016|MARBLE,travertine-009|Travertine 009|STONE,paving-stones-150|Paving Stones 150|STONE,paving-stones-138|Paving Stones 138|STONE,tiles-138|Tiles 138|TILE",
|
||||
],
|
||||
[
|
||||
"Ground / Road",
|
||||
"ground-103|Ground 103|GROUND,ground-037|Ground 037|GROUND,ground-054|Ground 054|GROUND,ground-104|Ground 104|GROUND,ground-068|Ground 068|GROUND,ground-080|Ground 080|GROUND,road-012-a|Road 012 A|ROAD,road-008-a|Road 008 A|ROAD,road-007|Road 007|ROAD,road-013-a|Road 013 A|ROAD,road-012-b|Road 012 B|ROAD,road-009-c|Road 009 C|ROAD,asphalt-031|Asphalt 031|ASPHALT",
|
||||
],
|
||||
[
|
||||
"Wood",
|
||||
"wood|Wood|SLICED,wood-094|Wood 094|WOOD,wood-092|Wood 092|WOOD,wood-051|Wood 051|WOOD,wood-066|Wood 066|WOOD,wood-049|Wood 049|WOOD,wood-058|Wood 058|WOOD,wood-floor-051|Wood Floor 051|FLOOR,wood-floor-064|Wood Floor 064|FLOOR,wood-floor-070|Wood Floor 070|FLOOR,bark-014|Bark 014|BARK",
|
||||
],
|
||||
[
|
||||
"Metal",
|
||||
"metal|Metal|FADING,metal-049-a|Metal 049 A|METAL,metal-055-a|Metal 055 A|METAL,metal-046-b|Metal 046 B|METAL,metal-061-b|Metal 061 B|METAL,metal-048-a|Metal 048 A|METAL,metal-032|Metal 032|METAL,metal-041-a|Metal 041 A|METAL,metal-038|Metal 038|METAL,diamond-plate-009|Diamond Plate 009|PLATE",
|
||||
],
|
||||
[
|
||||
"Organic / Soft",
|
||||
"lava|Lava|MOLTEN,grass-005|Grass 005|GRASS,grass-001|Grass 001|GRASS,grass-004|Grass 004|GRASS,carpet|Carpet|WOVEN,fabric-083|Fabric 083|FABRIC,snow|Snow|THAW,snow-015|Snow 015|SNOW,leather-037|Leather 037|LEATHER,fabric-080|Fabric 080|FABRIC",
|
||||
],
|
||||
];
|
||||
const textureGroups = document.getElementById("texture-groups");
|
||||
for (const [groupName, rows] of textureMaskTextGroups) {
|
||||
const items = rows.split(",").map((row) => {
|
||||
const [slug, title, sample] = row.split("|");
|
||||
return { slug, title, sample };
|
||||
});
|
||||
const panel = document.createElement("section");
|
||||
panel.className = "group-panel";
|
||||
|
||||
const groupHeader = document.createElement("div");
|
||||
groupHeader.className = "group-header";
|
||||
|
||||
const name = document.createElement("div");
|
||||
name.className = "group-name";
|
||||
name.textContent = groupName;
|
||||
|
||||
const count = document.createElement("div");
|
||||
count.className = "group-count";
|
||||
count.textContent = `${items.length} masks`;
|
||||
|
||||
groupHeader.append(name, count);
|
||||
|
||||
const groupGrid = document.createElement("div");
|
||||
groupGrid.className = "group-grid";
|
||||
|
||||
for (const item of items) {
|
||||
const cell = document.createElement("div");
|
||||
cell.className = "cell";
|
||||
|
||||
const label = document.createElement("div");
|
||||
label.className = "label";
|
||||
label.textContent = item.title;
|
||||
|
||||
const shadowWrap = document.createElement("div");
|
||||
shadowWrap.className = "shadow-wrap";
|
||||
|
||||
const sample = document.createElement("div");
|
||||
sample.className = "hf-texture-text sample";
|
||||
sample.style.webkitMaskImage = `url("masks/${item.slug}.png")`;
|
||||
sample.style.maskImage = `url("masks/${item.slug}.png")`;
|
||||
sample.textContent = item.sample;
|
||||
|
||||
shadowWrap.appendChild(sample);
|
||||
cell.append(label, shadowWrap);
|
||||
groupGrid.appendChild(cell);
|
||||
}
|
||||
|
||||
panel.append(groupHeader, groupGrid);
|
||||
textureGroups.appendChild(panel);
|
||||
}
|
||||
|
||||
window.__timelines = window.__timelines || {};
|
||||
const textureMaskTextTimeline = {
|
||||
play() {},
|
||||
pause() {},
|
||||
seek() {},
|
||||
time() {
|
||||
return 0;
|
||||
},
|
||||
duration() {
|
||||
return 5;
|
||||
},
|
||||
add() {},
|
||||
paused() {},
|
||||
set() {},
|
||||
};
|
||||
window.__timelines["texture-mask-text-demo"] = textureMaskTextTimeline;
|
||||
</script>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 159 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 176 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 118 KiB |
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 118 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 195 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 115 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 110 KiB |
|
After Width: | Height: | Size: 109 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 131 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 110 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 41 KiB |
@@ -0,0 +1,443 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "texture-mask-text",
|
||||
"type": "hyperframes:component",
|
||||
"title": "Texture Mask Text",
|
||||
"description": "CSS luminance masks that cut holes through letterforms - 66 pre-built texture masks from ambientCG PBR color maps",
|
||||
"tags": ["text", "texture", "mask", "effect"],
|
||||
"textureGroups": [
|
||||
{
|
||||
"title": "Masonry",
|
||||
"items": [
|
||||
"brick",
|
||||
"bricks-104",
|
||||
"bricks-102",
|
||||
"bricks-101",
|
||||
"bricks-075-a",
|
||||
"concrete",
|
||||
"concrete-034",
|
||||
"concrete-047-a",
|
||||
"concrete-046",
|
||||
"concrete-042-a",
|
||||
"plaster-001",
|
||||
"painted-plaster-017"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Stone",
|
||||
"items": [
|
||||
"rock",
|
||||
"rock-063",
|
||||
"rock-058",
|
||||
"onyx",
|
||||
"marble-012",
|
||||
"marble-016",
|
||||
"travertine-009",
|
||||
"paving-stones-150",
|
||||
"paving-stones-138",
|
||||
"tiles-138"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Ground / Road",
|
||||
"items": [
|
||||
"ground-103",
|
||||
"ground-037",
|
||||
"ground-054",
|
||||
"ground-104",
|
||||
"ground-068",
|
||||
"ground-080",
|
||||
"road-012-a",
|
||||
"road-008-a",
|
||||
"road-007",
|
||||
"road-013-a",
|
||||
"road-012-b",
|
||||
"road-009-c",
|
||||
"asphalt-031"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Wood",
|
||||
"items": [
|
||||
"wood",
|
||||
"wood-094",
|
||||
"wood-092",
|
||||
"wood-051",
|
||||
"wood-066",
|
||||
"wood-049",
|
||||
"wood-058",
|
||||
"wood-floor-051",
|
||||
"wood-floor-064",
|
||||
"wood-floor-070",
|
||||
"bark-014"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Metal",
|
||||
"items": [
|
||||
"metal",
|
||||
"metal-049-a",
|
||||
"metal-055-a",
|
||||
"metal-046-b",
|
||||
"metal-061-b",
|
||||
"metal-048-a",
|
||||
"metal-032",
|
||||
"metal-041-a",
|
||||
"metal-038",
|
||||
"diamond-plate-009"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Organic / Soft",
|
||||
"items": [
|
||||
"lava",
|
||||
"grass-005",
|
||||
"grass-001",
|
||||
"grass-004",
|
||||
"carpet",
|
||||
"fabric-083",
|
||||
"snow",
|
||||
"snow-015",
|
||||
"leather-037",
|
||||
"fabric-080"
|
||||
]
|
||||
}
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"path": "texture-mask-text.html",
|
||||
"target": "compositions/components/texture-mask-text/texture-mask-text.html",
|
||||
"type": "hyperframes:snippet"
|
||||
},
|
||||
{
|
||||
"path": "masks/lava.png",
|
||||
"target": "assets/texture-mask-text/masks/lava.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/rock.png",
|
||||
"target": "assets/texture-mask-text/masks/rock.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/concrete.png",
|
||||
"target": "assets/texture-mask-text/masks/concrete.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/brick.png",
|
||||
"target": "assets/texture-mask-text/masks/brick.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/wood.png",
|
||||
"target": "assets/texture-mask-text/masks/wood.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/onyx.png",
|
||||
"target": "assets/texture-mask-text/masks/onyx.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/carpet.png",
|
||||
"target": "assets/texture-mask-text/masks/carpet.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/metal.png",
|
||||
"target": "assets/texture-mask-text/masks/metal.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/snow.png",
|
||||
"target": "assets/texture-mask-text/masks/snow.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/bricks-104.png",
|
||||
"target": "assets/texture-mask-text/masks/bricks-104.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/ground-103.png",
|
||||
"target": "assets/texture-mask-text/masks/ground-103.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/grass-005.png",
|
||||
"target": "assets/texture-mask-text/masks/grass-005.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/rock-063.png",
|
||||
"target": "assets/texture-mask-text/masks/rock-063.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/wood-094.png",
|
||||
"target": "assets/texture-mask-text/masks/wood-094.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/road-012-a.png",
|
||||
"target": "assets/texture-mask-text/masks/road-012-a.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/wood-092.png",
|
||||
"target": "assets/texture-mask-text/masks/wood-092.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/metal-049-a.png",
|
||||
"target": "assets/texture-mask-text/masks/metal-049-a.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/wood-floor-051.png",
|
||||
"target": "assets/texture-mask-text/masks/wood-floor-051.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/paving-stones-150.png",
|
||||
"target": "assets/texture-mask-text/masks/paving-stones-150.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/road-008-a.png",
|
||||
"target": "assets/texture-mask-text/masks/road-008-a.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/road-007.png",
|
||||
"target": "assets/texture-mask-text/masks/road-007.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/metal-055-a.png",
|
||||
"target": "assets/texture-mask-text/masks/metal-055-a.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/concrete-034.png",
|
||||
"target": "assets/texture-mask-text/masks/concrete-034.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/grass-001.png",
|
||||
"target": "assets/texture-mask-text/masks/grass-001.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/wood-051.png",
|
||||
"target": "assets/texture-mask-text/masks/wood-051.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/asphalt-031.png",
|
||||
"target": "assets/texture-mask-text/masks/asphalt-031.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/wood-floor-064.png",
|
||||
"target": "assets/texture-mask-text/masks/wood-floor-064.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/concrete-047-a.png",
|
||||
"target": "assets/texture-mask-text/masks/concrete-047-a.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/metal-046-b.png",
|
||||
"target": "assets/texture-mask-text/masks/metal-046-b.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/grass-004.png",
|
||||
"target": "assets/texture-mask-text/masks/grass-004.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/marble-012.png",
|
||||
"target": "assets/texture-mask-text/masks/marble-012.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/ground-037.png",
|
||||
"target": "assets/texture-mask-text/masks/ground-037.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/tiles-138.png",
|
||||
"target": "assets/texture-mask-text/masks/tiles-138.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/wood-066.png",
|
||||
"target": "assets/texture-mask-text/masks/wood-066.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/concrete-046.png",
|
||||
"target": "assets/texture-mask-text/masks/concrete-046.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/wood-049.png",
|
||||
"target": "assets/texture-mask-text/masks/wood-049.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/road-013-a.png",
|
||||
"target": "assets/texture-mask-text/masks/road-013-a.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/plaster-001.png",
|
||||
"target": "assets/texture-mask-text/masks/plaster-001.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/snow-015.png",
|
||||
"target": "assets/texture-mask-text/masks/snow-015.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/road-012-b.png",
|
||||
"target": "assets/texture-mask-text/masks/road-012-b.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/concrete-042-a.png",
|
||||
"target": "assets/texture-mask-text/masks/concrete-042-a.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/bricks-102.png",
|
||||
"target": "assets/texture-mask-text/masks/bricks-102.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/rock-058.png",
|
||||
"target": "assets/texture-mask-text/masks/rock-058.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/wood-floor-070.png",
|
||||
"target": "assets/texture-mask-text/masks/wood-floor-070.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/bricks-101.png",
|
||||
"target": "assets/texture-mask-text/masks/bricks-101.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/travertine-009.png",
|
||||
"target": "assets/texture-mask-text/masks/travertine-009.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/ground-054.png",
|
||||
"target": "assets/texture-mask-text/masks/ground-054.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/wood-058.png",
|
||||
"target": "assets/texture-mask-text/masks/wood-058.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/ground-104.png",
|
||||
"target": "assets/texture-mask-text/masks/ground-104.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/painted-plaster-017.png",
|
||||
"target": "assets/texture-mask-text/masks/painted-plaster-017.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/paving-stones-138.png",
|
||||
"target": "assets/texture-mask-text/masks/paving-stones-138.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/ground-068.png",
|
||||
"target": "assets/texture-mask-text/masks/ground-068.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/bricks-075-a.png",
|
||||
"target": "assets/texture-mask-text/masks/bricks-075-a.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/ground-080.png",
|
||||
"target": "assets/texture-mask-text/masks/ground-080.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/road-009-c.png",
|
||||
"target": "assets/texture-mask-text/masks/road-009-c.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/bark-014.png",
|
||||
"target": "assets/texture-mask-text/masks/bark-014.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/marble-016.png",
|
||||
"target": "assets/texture-mask-text/masks/marble-016.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/fabric-083.png",
|
||||
"target": "assets/texture-mask-text/masks/fabric-083.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/metal-061-b.png",
|
||||
"target": "assets/texture-mask-text/masks/metal-061-b.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/metal-048-a.png",
|
||||
"target": "assets/texture-mask-text/masks/metal-048-a.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/metal-032.png",
|
||||
"target": "assets/texture-mask-text/masks/metal-032.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/metal-041-a.png",
|
||||
"target": "assets/texture-mask-text/masks/metal-041-a.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/metal-038.png",
|
||||
"target": "assets/texture-mask-text/masks/metal-038.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/diamond-plate-009.png",
|
||||
"target": "assets/texture-mask-text/masks/diamond-plate-009.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/leather-037.png",
|
||||
"target": "assets/texture-mask-text/masks/leather-037.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "masks/fabric-080.png",
|
||||
"target": "assets/texture-mask-text/masks/fabric-080.png",
|
||||
"type": "hyperframes:asset"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,379 @@
|
||||
<!--
|
||||
Texture Mask Text - CSS luminance masks that cut holes through letterforms.
|
||||
|
||||
Usage: add class="hf-texture-text hf-texture-{name}" to any text element.
|
||||
Paste the real style element near the bottom of this file into your composition's HTML once.
|
||||
Texture PNGs install to assets/texture-mask-text/masks; these CSS classes
|
||||
reference them with project-root URLs so the CSS block can be pasted
|
||||
into any composition.
|
||||
|
||||
Available textures (66 total):
|
||||
Masonry:
|
||||
hf-texture-brick - mortar line patterns
|
||||
hf-texture-bricks-104 - ambientCG Bricks104
|
||||
hf-texture-bricks-102 - ambientCG Bricks102
|
||||
hf-texture-bricks-101 - ambientCG Bricks101
|
||||
hf-texture-bricks-075-a - ambientCG Bricks075A
|
||||
hf-texture-concrete - streaked channel cuts
|
||||
hf-texture-concrete-034 - ambientCG Concrete034
|
||||
hf-texture-concrete-047-a - ambientCG Concrete047A
|
||||
hf-texture-concrete-046 - ambientCG Concrete046
|
||||
hf-texture-concrete-042-a - ambientCG Concrete042A
|
||||
hf-texture-plaster-001 - ambientCG Plaster001
|
||||
hf-texture-painted-plaster-017 - ambientCG PaintedPlaster017
|
||||
|
||||
Stone:
|
||||
hf-texture-rock - sharp hard cutouts
|
||||
hf-texture-rock-063 - ambientCG Rock063
|
||||
hf-texture-rock-058 - ambientCG Rock058
|
||||
hf-texture-onyx - flowing marble veins
|
||||
hf-texture-marble-012 - ambientCG Marble012
|
||||
hf-texture-marble-016 - ambientCG Marble016
|
||||
hf-texture-travertine-009 - ambientCG Travertine009
|
||||
hf-texture-paving-stones-150 - ambientCG PavingStones150
|
||||
hf-texture-paving-stones-138 - ambientCG PavingStones138
|
||||
hf-texture-tiles-138 - ambientCG Tiles138
|
||||
|
||||
Ground / road:
|
||||
hf-texture-ground-103 - ambientCG Ground103
|
||||
hf-texture-ground-037 - ambientCG Ground037
|
||||
hf-texture-ground-054 - ambientCG Ground054
|
||||
hf-texture-ground-104 - ambientCG Ground104
|
||||
hf-texture-ground-068 - ambientCG Ground068
|
||||
hf-texture-ground-080 - ambientCG Ground080
|
||||
hf-texture-road-012-a - ambientCG Road012A
|
||||
hf-texture-road-008-a - ambientCG Road008A
|
||||
hf-texture-road-007 - ambientCG Road007
|
||||
hf-texture-road-013-a - ambientCG Road013A
|
||||
hf-texture-road-012-b - ambientCG Road012B
|
||||
hf-texture-road-009-c - ambientCG Road009C
|
||||
hf-texture-asphalt-031 - ambientCG Asphalt031
|
||||
|
||||
Wood:
|
||||
hf-texture-wood - horizontal grain slices
|
||||
hf-texture-wood-094 - ambientCG Wood094
|
||||
hf-texture-wood-092 - ambientCG Wood092
|
||||
hf-texture-wood-051 - ambientCG Wood051
|
||||
hf-texture-wood-066 - ambientCG Wood066
|
||||
hf-texture-wood-049 - ambientCG Wood049
|
||||
hf-texture-wood-058 - ambientCG Wood058
|
||||
hf-texture-wood-floor-051 - ambientCG WoodFloor051
|
||||
hf-texture-wood-floor-064 - ambientCG WoodFloor064
|
||||
hf-texture-wood-floor-070 - ambientCG WoodFloor070
|
||||
hf-texture-bark-014 - ambientCG Bark014
|
||||
|
||||
Metal:
|
||||
hf-texture-metal - rust and pit erosion
|
||||
hf-texture-metal-049-a - ambientCG Metal049A
|
||||
hf-texture-metal-055-a - ambientCG Metal055A
|
||||
hf-texture-metal-046-b - ambientCG Metal046B
|
||||
hf-texture-metal-061-b - ambientCG Metal061B
|
||||
hf-texture-metal-048-a - ambientCG Metal048A
|
||||
hf-texture-metal-032 - ambientCG Metal032
|
||||
hf-texture-metal-041-a - ambientCG Metal041A
|
||||
hf-texture-metal-038 - ambientCG Metal038
|
||||
hf-texture-diamond-plate-009 - ambientCG DiamondPlate009
|
||||
|
||||
Organic / soft:
|
||||
hf-texture-lava - smooth organic erosion
|
||||
hf-texture-grass-005 - ambientCG Grass005
|
||||
hf-texture-grass-001 - ambientCG Grass001
|
||||
hf-texture-grass-004 - ambientCG Grass004
|
||||
hf-texture-carpet - fine repeating weave
|
||||
hf-texture-fabric-083 - ambientCG Fabric083
|
||||
hf-texture-snow - soft patchy dissolve
|
||||
hf-texture-snow-015 - ambientCG Snow015
|
||||
hf-texture-leather-037 - ambientCG Leather037
|
||||
hf-texture-fabric-080 - ambientCG Fabric080
|
||||
|
||||
Custom properties:
|
||||
--mask-size cover CSS mask-size
|
||||
--mask-position center CSS mask-position
|
||||
--mask-contrast 1 filter contrast to sharpen/soften edges
|
||||
|
||||
Custom texture (set mask-image directly, not via custom property):
|
||||
<div class="hf-texture-text" style="-webkit-mask-image: url('my-texture.png'); mask-image: url('my-texture.png')">TEXT</div>
|
||||
|
||||
Drop shadow (follows masked contour, not bounding box):
|
||||
<div style="filter: drop-shadow(2px 3px 4px rgba(0,0,0,0.3))">
|
||||
<div class="hf-texture-text hf-texture-lava">TEXT</div>
|
||||
</div>
|
||||
-->
|
||||
|
||||
<style>
|
||||
.hf-texture-text {
|
||||
color: #fff;
|
||||
-webkit-mask-size: var(--mask-size, cover);
|
||||
mask-size: var(--mask-size, cover);
|
||||
-webkit-mask-position: var(--mask-position, center);
|
||||
mask-position: var(--mask-position, center);
|
||||
-webkit-mask-mode: luminance;
|
||||
mask-mode: luminance;
|
||||
filter: contrast(var(--mask-contrast, 1));
|
||||
}
|
||||
|
||||
.hf-texture-lava {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/lava.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/lava.png");
|
||||
}
|
||||
.hf-texture-rock {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/rock.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/rock.png");
|
||||
}
|
||||
.hf-texture-concrete {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/concrete.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/concrete.png");
|
||||
}
|
||||
.hf-texture-brick {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/brick.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/brick.png");
|
||||
}
|
||||
.hf-texture-wood {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/wood.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/wood.png");
|
||||
}
|
||||
.hf-texture-onyx {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/onyx.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/onyx.png");
|
||||
}
|
||||
.hf-texture-carpet {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/carpet.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/carpet.png");
|
||||
}
|
||||
.hf-texture-metal {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/metal.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/metal.png");
|
||||
}
|
||||
.hf-texture-snow {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/snow.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/snow.png");
|
||||
}
|
||||
.hf-texture-bricks-104 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/bricks-104.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/bricks-104.png");
|
||||
}
|
||||
.hf-texture-ground-103 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/ground-103.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/ground-103.png");
|
||||
}
|
||||
.hf-texture-grass-005 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/grass-005.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/grass-005.png");
|
||||
}
|
||||
.hf-texture-rock-063 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/rock-063.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/rock-063.png");
|
||||
}
|
||||
.hf-texture-wood-094 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/wood-094.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/wood-094.png");
|
||||
}
|
||||
.hf-texture-road-012-a {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/road-012-a.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/road-012-a.png");
|
||||
}
|
||||
.hf-texture-wood-092 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/wood-092.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/wood-092.png");
|
||||
}
|
||||
.hf-texture-metal-049-a {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/metal-049-a.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/metal-049-a.png");
|
||||
}
|
||||
.hf-texture-wood-floor-051 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/wood-floor-051.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/wood-floor-051.png");
|
||||
}
|
||||
.hf-texture-paving-stones-150 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/paving-stones-150.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/paving-stones-150.png");
|
||||
}
|
||||
.hf-texture-road-008-a {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/road-008-a.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/road-008-a.png");
|
||||
}
|
||||
.hf-texture-road-007 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/road-007.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/road-007.png");
|
||||
}
|
||||
.hf-texture-metal-055-a {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/metal-055-a.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/metal-055-a.png");
|
||||
}
|
||||
.hf-texture-concrete-034 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/concrete-034.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/concrete-034.png");
|
||||
}
|
||||
.hf-texture-grass-001 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/grass-001.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/grass-001.png");
|
||||
}
|
||||
.hf-texture-wood-051 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/wood-051.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/wood-051.png");
|
||||
}
|
||||
.hf-texture-asphalt-031 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/asphalt-031.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/asphalt-031.png");
|
||||
}
|
||||
.hf-texture-wood-floor-064 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/wood-floor-064.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/wood-floor-064.png");
|
||||
}
|
||||
.hf-texture-concrete-047-a {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/concrete-047-a.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/concrete-047-a.png");
|
||||
}
|
||||
.hf-texture-metal-046-b {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/metal-046-b.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/metal-046-b.png");
|
||||
}
|
||||
.hf-texture-grass-004 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/grass-004.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/grass-004.png");
|
||||
}
|
||||
.hf-texture-marble-012 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/marble-012.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/marble-012.png");
|
||||
}
|
||||
.hf-texture-ground-037 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/ground-037.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/ground-037.png");
|
||||
}
|
||||
.hf-texture-tiles-138 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/tiles-138.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/tiles-138.png");
|
||||
}
|
||||
.hf-texture-wood-066 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/wood-066.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/wood-066.png");
|
||||
}
|
||||
.hf-texture-concrete-046 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/concrete-046.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/concrete-046.png");
|
||||
}
|
||||
.hf-texture-wood-049 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/wood-049.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/wood-049.png");
|
||||
}
|
||||
.hf-texture-road-013-a {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/road-013-a.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/road-013-a.png");
|
||||
}
|
||||
.hf-texture-plaster-001 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/plaster-001.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/plaster-001.png");
|
||||
}
|
||||
.hf-texture-snow-015 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/snow-015.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/snow-015.png");
|
||||
}
|
||||
.hf-texture-road-012-b {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/road-012-b.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/road-012-b.png");
|
||||
}
|
||||
.hf-texture-concrete-042-a {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/concrete-042-a.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/concrete-042-a.png");
|
||||
}
|
||||
.hf-texture-bricks-102 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/bricks-102.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/bricks-102.png");
|
||||
}
|
||||
.hf-texture-rock-058 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/rock-058.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/rock-058.png");
|
||||
}
|
||||
.hf-texture-wood-floor-070 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/wood-floor-070.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/wood-floor-070.png");
|
||||
}
|
||||
.hf-texture-bricks-101 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/bricks-101.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/bricks-101.png");
|
||||
}
|
||||
.hf-texture-travertine-009 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/travertine-009.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/travertine-009.png");
|
||||
}
|
||||
.hf-texture-ground-054 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/ground-054.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/ground-054.png");
|
||||
}
|
||||
.hf-texture-wood-058 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/wood-058.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/wood-058.png");
|
||||
}
|
||||
.hf-texture-ground-104 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/ground-104.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/ground-104.png");
|
||||
}
|
||||
.hf-texture-painted-plaster-017 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/painted-plaster-017.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/painted-plaster-017.png");
|
||||
}
|
||||
.hf-texture-paving-stones-138 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/paving-stones-138.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/paving-stones-138.png");
|
||||
}
|
||||
.hf-texture-ground-068 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/ground-068.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/ground-068.png");
|
||||
}
|
||||
.hf-texture-bricks-075-a {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/bricks-075-a.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/bricks-075-a.png");
|
||||
}
|
||||
.hf-texture-ground-080 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/ground-080.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/ground-080.png");
|
||||
}
|
||||
.hf-texture-road-009-c {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/road-009-c.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/road-009-c.png");
|
||||
}
|
||||
.hf-texture-bark-014 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/bark-014.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/bark-014.png");
|
||||
}
|
||||
.hf-texture-marble-016 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/marble-016.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/marble-016.png");
|
||||
}
|
||||
.hf-texture-fabric-083 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/fabric-083.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/fabric-083.png");
|
||||
}
|
||||
.hf-texture-metal-061-b {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/metal-061-b.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/metal-061-b.png");
|
||||
}
|
||||
.hf-texture-metal-048-a {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/metal-048-a.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/metal-048-a.png");
|
||||
}
|
||||
.hf-texture-metal-032 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/metal-032.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/metal-032.png");
|
||||
}
|
||||
.hf-texture-metal-041-a {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/metal-041-a.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/metal-041-a.png");
|
||||
}
|
||||
.hf-texture-metal-038 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/metal-038.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/metal-038.png");
|
||||
}
|
||||
.hf-texture-diamond-plate-009 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/diamond-plate-009.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/diamond-plate-009.png");
|
||||
}
|
||||
.hf-texture-leather-037 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/leather-037.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/leather-037.png");
|
||||
}
|
||||
.hf-texture-fabric-080 {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/fabric-080.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/fabric-080.png");
|
||||
}
|
||||
</style>
|
||||
@@ -59,6 +59,10 @@
|
||||
"name": "grid-pixelate-wipe",
|
||||
"type": "hyperframes:component"
|
||||
},
|
||||
{
|
||||
"name": "texture-mask-text",
|
||||
"type": "hyperframes:component"
|
||||
},
|
||||
{
|
||||
"name": "instagram-follow",
|
||||
"type": "hyperframes:block"
|
||||
|
||||
@@ -38,6 +38,11 @@ interface SourceMetadata {
|
||||
sourcePrompt?: string;
|
||||
}
|
||||
|
||||
interface TextureGroup {
|
||||
title: string;
|
||||
items: string[];
|
||||
}
|
||||
|
||||
interface CatalogEntry {
|
||||
name: string;
|
||||
type: ItemKind;
|
||||
@@ -97,23 +102,233 @@ function typeDir(kind: ItemKind): string {
|
||||
return ITEM_TYPE_DIRS[kind === "block" ? "hyperframes:block" : "hyperframes:component"];
|
||||
}
|
||||
|
||||
function textureGroupsFor(manifest: RegistryItem): TextureGroup[] {
|
||||
if (!("textureGroups" in manifest)) return [];
|
||||
const value = manifest.textureGroups;
|
||||
if (!Array.isArray(value)) return [];
|
||||
|
||||
return value.filter((group): group is TextureGroup => {
|
||||
if (!group || typeof group !== "object") return false;
|
||||
if (!("title" in group) || typeof group.title !== "string") return false;
|
||||
if (!("items" in group) || !Array.isArray(group.items)) return false;
|
||||
return group.items.every((item) => typeof item === "string");
|
||||
});
|
||||
}
|
||||
|
||||
function textureLabel(slug: string): string {
|
||||
return slug
|
||||
.split("-")
|
||||
.map((part) =>
|
||||
part.length === 1 ? part.toUpperCase() : part[0]!.toUpperCase() + part.slice(1),
|
||||
)
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
function textureSampleWord(slug: string): string {
|
||||
if (slug.includes("brick")) return "BRICK";
|
||||
if (slug.includes("concrete")) return "CONCRETE";
|
||||
if (slug.includes("plaster")) return "PLASTER";
|
||||
if (slug.includes("rock")) return "ROCK";
|
||||
if (slug.includes("onyx")) return "ONYX";
|
||||
if (slug.includes("marble")) return "MARBLE";
|
||||
if (slug.includes("travertine")) return "STONE";
|
||||
if (slug.includes("paving")) return "STONE";
|
||||
if (slug.includes("tiles")) return "TILE";
|
||||
if (slug.includes("ground")) return "GROUND";
|
||||
if (slug.includes("road")) return "ROAD";
|
||||
if (slug.includes("asphalt")) return "ASPHALT";
|
||||
if (slug.includes("wood-floor")) return "FLOOR";
|
||||
if (slug.includes("wood")) return "WOOD";
|
||||
if (slug.includes("bark")) return "BARK";
|
||||
if (slug.includes("diamond")) return "PLATE";
|
||||
if (slug.includes("metal")) return "METAL";
|
||||
if (slug.includes("lava")) return "LAVA";
|
||||
if (slug.includes("grass")) return "GRASS";
|
||||
if (slug.includes("carpet")) return "WOVEN";
|
||||
if (slug.includes("fabric")) return "FABRIC";
|
||||
if (slug.includes("snow")) return "SNOW";
|
||||
if (slug.includes("leather")) return "LEATHER";
|
||||
return slug.toUpperCase();
|
||||
}
|
||||
|
||||
function textureMaskUrlFor(manifest: RegistryItem, texture: string): string {
|
||||
return `${catalogImageBase}/components/${manifest.name}/masks/${texture}.png`;
|
||||
}
|
||||
|
||||
function generateTextureExamples(manifest: RegistryItem, textureGroups: TextureGroup[]): string[] {
|
||||
const lines: string[] = [
|
||||
"## Texture Examples",
|
||||
"",
|
||||
'<div className="hf-texture-example-groups">',
|
||||
];
|
||||
|
||||
for (const group of textureGroups) {
|
||||
lines.push(
|
||||
" <div>",
|
||||
` <h3 className="hf-texture-example-title">${group.title}</h3>`,
|
||||
' <div className="hf-texture-example-grid">',
|
||||
);
|
||||
for (const item of group.items) {
|
||||
const maskPath = textureMaskUrlFor(manifest, item);
|
||||
const textureClass = `hf-texture-${item}`;
|
||||
lines.push(
|
||||
` <div className="hf-texture-example-card" style={{ "--mask-url": "url('${maskPath}')" }}>`,
|
||||
` <div className="hf-texture-example-meta"><div className="hf-texture-example-label">${textureLabel(item)}</div><code className="hf-texture-example-class">${textureClass}</code></div>`,
|
||||
` <div className="hf-texture-example-shadow"><div className="hf-texture-example-word">${textureSampleWord(item)}</div></div>`,
|
||||
` <div className="hf-texture-example-usage">Use <code>hf-texture-text ${textureClass}</code></div>`,
|
||||
" </div>",
|
||||
);
|
||||
}
|
||||
lines.push(" </div>", " </div>");
|
||||
}
|
||||
|
||||
lines.push("</div>", "");
|
||||
return lines;
|
||||
}
|
||||
|
||||
function generateTextureAgentUsage(
|
||||
manifest: RegistryItem,
|
||||
textureGroups: TextureGroup[],
|
||||
): string[] {
|
||||
const firstTexture = textureGroups[0]?.items[0] ?? "brick";
|
||||
const firstClass = `hf-texture-${firstTexture}`;
|
||||
const installedSnippet = `compositions/components/${manifest.name}/${manifest.name}.html`;
|
||||
|
||||
return [
|
||||
"## Agent Usage",
|
||||
"",
|
||||
"Use this wording when asking an agent to apply a texture:",
|
||||
"",
|
||||
"```text",
|
||||
`Use the ${manifest.title} catalog component.`,
|
||||
"",
|
||||
"1. From the project root, run:",
|
||||
` npx hyperframes add ${manifest.name}`,
|
||||
"2. That command creates this installed snippet:",
|
||||
` ${installedSnippet}`,
|
||||
"3. Open that file and paste the real <style> block",
|
||||
" near the bottom into the composition once. That CSS defines",
|
||||
" hf-texture-text and every hf-texture-* class.",
|
||||
"4. Apply this class to the target text:",
|
||||
` class="hf-texture-text ${firstClass}"`,
|
||||
"5. For another material, copy one hf-texture-* class",
|
||||
" from the Texture Examples cards.",
|
||||
"6. This is the proper way to apply drop shadow",
|
||||
" to textured text: wrap the text and put",
|
||||
" filter on the wrapper, not on the text.",
|
||||
" Use this markup:",
|
||||
` <div style="filter: drop-shadow(1px 2px 1px rgba(0,0,0,0.48))">`,
|
||||
` <div class="hf-texture-text ${firstClass}">TEXT</div>`,
|
||||
" </div>",
|
||||
"```",
|
||||
"",
|
||||
`After install, the snippet lives at \`${installedSnippet}\` inside the project where you ran \`npx hyperframes add ${manifest.name}\`. The part to paste is the real \`<style>\` element near the bottom of that file; the texture PNGs install to \`assets/${manifest.name}/masks/\` and are referenced by project-root URLs in that CSS.`,
|
||||
"",
|
||||
`Swap \`${firstClass}\` for the class shown on any texture card below. The base class \`hf-texture-text\` is always required.`,
|
||||
"",
|
||||
];
|
||||
}
|
||||
|
||||
function generateTextureAnimationExample(
|
||||
manifest: RegistryItem,
|
||||
textureGroups: TextureGroup[],
|
||||
): string[] {
|
||||
const texture =
|
||||
textureGroups.flatMap((group) => group.items).find((item) => item === "lava") ??
|
||||
textureGroups[0]?.items[0] ??
|
||||
"brick";
|
||||
const textureClass = `hf-texture-${texture}`;
|
||||
const maskPath = textureMaskUrlFor(manifest, texture);
|
||||
|
||||
return [
|
||||
"## Animated Texture",
|
||||
"",
|
||||
"Animate the texture by moving the mask position on the text element. Keep drop shadow on a wrapper so the shadow follows the textured contour.",
|
||||
"",
|
||||
`<div className="hf-texture-animate-demo" style={{ "--mask-url": "url('${maskPath}')" }}>`,
|
||||
' <div className="hf-texture-animate-meta">',
|
||||
' <div className="hf-texture-animate-label">Animated mask position</div>',
|
||||
` <code className="hf-texture-animate-class">hf-texture-text ${textureClass}</code>`,
|
||||
" </div>",
|
||||
' <div className="hf-texture-animate-shadow">',
|
||||
' <div className="hf-texture-animate-word">MOTION</div>',
|
||||
" </div>",
|
||||
"</div>",
|
||||
"",
|
||||
"```html",
|
||||
'<div class="texture-shadow">',
|
||||
` <div class="hf-texture-text ${textureClass} animated-texture">MOTION</div>`,
|
||||
"</div>",
|
||||
"```",
|
||||
"",
|
||||
"```css",
|
||||
".animated-texture {",
|
||||
" --mask-size: 180% 180%;",
|
||||
" --mask-position: 0% 50%;",
|
||||
"}",
|
||||
"```",
|
||||
"",
|
||||
"```js",
|
||||
"const tl = gsap.timeline({ paused: true });",
|
||||
'tl.to(".animated-texture", {',
|
||||
' "--mask-position": "100% 50%",',
|
||||
" duration: 1.2,",
|
||||
' ease: "sine.inOut",',
|
||||
" yoyo: true,",
|
||||
" repeat: 1,",
|
||||
"}, 0);",
|
||||
'window.__timelines["my-composition"] = tl;',
|
||||
"```",
|
||||
"",
|
||||
];
|
||||
}
|
||||
|
||||
function generateTexturePreview(manifest: RegistryItem, textureGroups: TextureGroup[]): string[] {
|
||||
const sampleItems = textureGroups
|
||||
.map((group) => group.items[0])
|
||||
.filter(Boolean)
|
||||
.slice(0, 6);
|
||||
const lines: string[] = ['<div className="hf-texture-preview-panel">'];
|
||||
|
||||
for (const item of sampleItems) {
|
||||
const maskPath = textureMaskUrlFor(manifest, item);
|
||||
lines.push(
|
||||
` <div className="hf-texture-preview-card" style={{ "--mask-url": "url('${maskPath}')" }}>`,
|
||||
` <div className="hf-texture-preview-label">${textureLabel(item!)}</div>`,
|
||||
` <div className="hf-texture-preview-shadow"><div className="hf-texture-preview-word">${textureSampleWord(item!)}</div></div>`,
|
||||
" </div>",
|
||||
);
|
||||
}
|
||||
|
||||
lines.push("</div>", "");
|
||||
return lines;
|
||||
}
|
||||
|
||||
function catalogPreviewFor(kind: ItemKind, manifest: RegistryItem): string {
|
||||
const dir = typeDir(kind);
|
||||
return `${catalogImageBase}/${dir}/${manifest.name}.png`;
|
||||
}
|
||||
|
||||
function yamlString(value: string): string {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
function generateItemMdx(kind: ItemKind, manifest: RegistryItem): string {
|
||||
const tags = manifest.tags ?? [];
|
||||
const tagBadges = tags.map((t) => `\`${t}\``).join(" ");
|
||||
const installCmd = `npx hyperframes add ${manifest.name}`;
|
||||
const source = manifest as RegistryItem & SourceMetadata;
|
||||
const textureGroups = textureGroupsFor(manifest);
|
||||
|
||||
const lines: string[] = [
|
||||
"---",
|
||||
`title: "${manifest.title.replace(/"/g, '\\"')}"`,
|
||||
`description: "${manifest.description.replace(/"/g, '\\"')}"`,
|
||||
"---",
|
||||
"",
|
||||
`# ${manifest.title}`,
|
||||
"",
|
||||
manifest.description,
|
||||
"",
|
||||
];
|
||||
const lines: string[] = ["---", `title: ${yamlString(manifest.title)}`];
|
||||
if (textureGroups.length === 0) {
|
||||
lines.push(`description: ${yamlString(manifest.description)}`);
|
||||
}
|
||||
lines.push("---", "");
|
||||
|
||||
if (textureGroups.length === 0) {
|
||||
lines.push(`# ${manifest.title}`, "", manifest.description, "");
|
||||
}
|
||||
|
||||
if (tagBadges) {
|
||||
lines.push(tagBadges, "");
|
||||
@@ -137,12 +352,16 @@ function generateItemMdx(kind: ItemKind, manifest: RegistryItem): string {
|
||||
lines.push("## Source Prompt", "", "```text", source.sourcePrompt, "```", "");
|
||||
}
|
||||
|
||||
if (textureGroups.length > 0) {
|
||||
lines.push(...generateTexturePreview(manifest, textureGroups));
|
||||
} else {
|
||||
// Preview video with poster — muted loop, no autoPlay (matches examples page).
|
||||
const previewPath = `${catalogImageBase}/${typeDir(kind)}/${manifest.name}`;
|
||||
lines.push(
|
||||
`<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="${previewPath}.mp4" poster="${previewPath}.png" autoPlay muted loop playsInline />`,
|
||||
"",
|
||||
);
|
||||
}
|
||||
|
||||
// Install command
|
||||
lines.push(
|
||||
@@ -181,12 +400,20 @@ function generateItemMdx(kind: ItemKind, manifest: RegistryItem): string {
|
||||
);
|
||||
}
|
||||
|
||||
if (textureGroups.length > 0) {
|
||||
lines.push(...generateTextureAgentUsage(manifest, textureGroups));
|
||||
lines.push(...generateTextureAnimationExample(manifest, textureGroups));
|
||||
lines.push(...generateTextureExamples(manifest, textureGroups));
|
||||
}
|
||||
|
||||
// Files
|
||||
if (textureGroups.length === 0) {
|
||||
lines.push("## Files", "", "| File | Target | Type |", "| --- | --- | --- |");
|
||||
for (const f of manifest.files) {
|
||||
lines.push(`| \`${f.path}\` | \`${f.target}\` | ${f.type} |`);
|
||||
}
|
||||
lines.push("");
|
||||
}
|
||||
|
||||
// Usage hint — find the primary file by type, not array position.
|
||||
const primaryFile =
|
||||
@@ -208,6 +435,14 @@ function generateItemMdx(kind: ItemKind, manifest: RegistryItem): string {
|
||||
"```",
|
||||
"",
|
||||
);
|
||||
} else {
|
||||
if (textureGroups.length > 0) {
|
||||
lines.push(
|
||||
"## Usage",
|
||||
"",
|
||||
`After \`${installCmd}\`, the installed snippet lives at \`${primaryTarget}\` inside your current HyperFrames project. Open that file and paste the real \`<style>\` element near the bottom into your composition once; it defines \`hf-texture-text\` and every \`hf-texture-*\` class used by the examples above. Keep the installed texture PNGs in \`assets/${manifest.name}/masks/\`; the CSS references them with project-root URLs.`,
|
||||
"",
|
||||
);
|
||||
} else {
|
||||
lines.push(
|
||||
"## Usage",
|
||||
@@ -216,6 +451,7 @@ function generateItemMdx(kind: ItemKind, manifest: RegistryItem): string {
|
||||
"",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Related skill
|
||||
if (manifest.relatedSkill) {
|
||||
@@ -258,7 +494,7 @@ function main(): void {
|
||||
description: manifest.description,
|
||||
tags: manifest.tags ?? [],
|
||||
href: `/catalog/${dir}/${manifest.name}`,
|
||||
preview: `${catalogImageBase}/${dir}/${manifest.name}.png`,
|
||||
preview: catalogPreviewFor(kind, manifest),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||