Good Fox
aadmin2026-11-05 发布Site模板2 阅读

Good Fox

类型:Site模板 · 收录:2026-11-05 01:12:40 · 源码:会员可见

[hidecontent type="vip1"]
Build a single standalone index.html file: a full-screen hero section called "GOOD FOX" for a site named "Crafts Atelier". Use only plain HTML, CSS and JavaScript, with no frameworks or build step. Put all CSS in one <style> tag and all JavaScript in one IIFE <script> at the end of <body>, wrapped in (() => { "use strict"; ... })(). Do not download any local assets. Load both images straight from the CloudFront URLs below. Do NOT add any CRT, scanline, chromatic-aberration, grain or WebGL post-processing. Do NOT add any auto-play or idle animation of the cursor; the effects run only while the user hovers or touches.

==================================================
1. ASSETS (use exactly these URLs)
==================================================
Both images are 1024x1024 transparent-background PNG cutouts. They are pixel-aligned, so the same head, chain and cross sit in the same spots on both.
- FRONT (white angel fox: white fur, white feathered wings at lower left and right, gold Cuban chain, gold cross with pearl inlays, amber eyes; there is NO halo in the PNG):
  https://d8j0ntlcm91z4.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/hf_20261019_144902_95c42d4b-a268-49cb-82c6-5c7688018feb.png
- BACK (black fox drawn in engraved ink style with hot-pink rim light, pink eyes, silver chain, silver cross with pink gems, no wings):
  https://d8j0ntlcm91z4.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/hf_20261019_144904_f862cff1-c396-4e6e-91ed-1b5b0eb085a7.png
Load both with `img.crossOrigin = "anonymous"` (the CDN sends Access-Control-Allow-Origin: *) and decoding = "async".

==================================================
2. FONTS
==================================================
Google Fonts:
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Anton&family=Space+Mono:wght@400;700&display=swap" rel="stylesheet">
- "Anton" 400 is the giant GOOD / FOX title, drawn on canvas.
- "Space Mono" is all the small uppercase UI text.
Before the first canvas draw, wait for document.fonts.load('400 100px Anton').

==================================================
3. HEAD / META / COPY (use exactly this text)
==================================================
<html lang="en">, meta charset utf-8, and viewport "width=device-width, initial-scale=1, viewport-fit=cover".
<title>Good Fox — Crafts Atelier</title>
<meta name="description" content="Makers & Craftsmen Crafts Atelier — Good Fox hero with cursor-path dark-twin reveal.">

Body markup:
<main class="hero" id="hero">
  <h1 class="sr-only">Good Fox — Makers & Craftsmen Crafts Atelier</h1>
  <canvas id="view" role="img" aria-label="A pale winged fox with a halo and gold cross; dragging the mouse unveils its dark twin, an ink fox with a pink cross on neon pink."></canvas>

  <div class="ui ui-tl">
    <button class="sound" id="sound" type="button" aria-pressed="false">
      <svg viewBox="0 0 26 22" fill="none" aria-hidden="true">
        <path d="M2 8h4l6-5v16l-6-5H2z" fill="currentColor"/>
        <g class="mute" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="M17 13l5 5M22 13l-5 5"/></g>
        <g class="wave" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" fill="none"><path d="M16 7.5a5 5 0 0 1 0 7"/><path d="M19 4.5a9 9 0 0 1 0 13"/></g>
      </svg>
      <span id="soundLabel">Audio off</span>
    </button>
  </div>
  <div class="ui ui-tr">Makers & Craftsmen<br>Crafts Atelier</div>
  <div class="ui ui-bl"><span class="dash"></span>Workshop Formation<br>Akin to an Arts Guild</div>
  <div class="loader" id="loader">Opening</div>
</main>

Sound button (UI only, no audio): a click toggles aria-pressed between true and false and swaps the label between "Audio on" and "Audio off". If the images fail to load, set the loader text to "Unable to fetch media".

==================================================
4. CSS (exact)
==================================================
:root { --front:#29a3f4; --back:#ff0a8c; --ink:#ffffff; --gutter:clamp(16px, 2.2vw, 32px); }
* { box-sizing:border-box; margin:0; padding:0; }
html, body { height:100%; }
body { background:var(--front); color:var(--ink); font-family:"Space Mono", ui-monospace, monospace; overflow:hidden; -webkit-font-smoothing:antialiased; }
.hero { position:relative; width:100vw; height:100vh; height:100svh; overflow:hidden; background:var(--front); touch-action:none; cursor:crosshair; }
.hero canvas { position:absolute; inset:0; width:100%; height:100%; display:block; }
.sr-only { position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; }
.ui { position:absolute; z-index:3; font-size:12px; line-height:1.6; letter-spacing:.14em; text-transform:uppercase; pointer-events:none; text-shadow:0 0 1px rgba(255,255,255,.35); }
.ui > * { pointer-events:auto; }
.ui-tl { top:var(--gutter); left:var(--gutter); }
.ui-tr { top:var(--gutter); right:var(--gutter); text-align:right; }
.ui-bl { bottom:var(--gutter); left:var(--gutter); }
.sound { display:inline-flex; align-items:center; gap:14px; background:none; border:0; color:inherit; font:inherit; letter-spacing:inherit; text-transform:inherit; cursor:pointer; padding:4px 0; }
.sound svg { width:26px; height:22px; flex:none; }
.sound .wave { display:none; }
.sound[aria-pressed="true"] .wave { display:block; }
.sound[aria-pressed="true"] .mute { display:none; }
.sound:focus-visible { outline:1px dashed #fff; outline-offset:4px; }
.dash { display:block; width:26px; height:2px; background:currentColor; margin-bottom:18px; }
.loader { position:absolute; inset:0; z-index:4; display:grid; place-items:center; background:var(--front); font-size:11px; letter-spacing:.3em; text-transform:uppercase; transition:opacity .8s ease, visibility .8s; }
.loader.done { opacity:0; visibility:hidden; }
@media (max-width:640px) {
  .ui { font-size:10px; letter-spacing:.12em; }
  .sound { gap:10px; }
  .sound svg { width:22px; height:18px; }
  .dash { width:22px; margin-bottom:12px; }
}

==================================================
5. RENDERING ARCHITECTURE (Canvas 2D only)
==================================================
Constants: COLORS = { front:"#29a3f4", back:"#ff0a8c", textFront:"#ffffff", textBack:"#fff4f9" }, WORDS = ["GOOD","FOX"], FONT = "Anton".

Sizing (on load, and on window resize debounced to 120ms):
- w,h = hero.clientWidth/clientHeight (fall back to innerWidth/innerHeight; if 0, retry in 100ms).
- dpr = min(devicePixelRatio, 2). Pixel budget: maxPx = 2.4e6 if w<760, else 3.4e6. If w*h*dpr² > maxPx, set dpr = sqrt(maxPx/(w*h)).
- W = round(w*dpr), H = round(h*dpr). The visible #view canvas is W x H and is the composite target.
- Offscreen canvases, all W x H: bufB (back scene), textA (front text), textB (back text), staticA (clean text source).

Layout (computeLayout). Measure "GOOD" in Anton at 100px to get wGood (width per px of font size). portrait = h > w*1.1.
- LANDSCAPE:
  imgSize s = min(h*1.02, w*0.62); img.x = (w-s)/2; img.y = h - s*0.98 (fox is bottom-anchored and slightly cropped).
  gap = s*0.15; avail = w/2 - gap - w*0.035; fontSize fs = min(avail/wGood, h*0.46).
  Vertical text centre cy = img.y + s*0.36 (around eye level).
  "GOOD" is right-aligned at x = w/2 - gap; "FOX" is left-aligned at x = w/2 + gap. Both are centred vertically on cy.
  The fox head overlaps and hides the inner ends of both words. The text is BEHIND the fox.
- PORTRAIT (mobile):
  s = min(w*1.18, h*0.64); img.x = (w-s)/2; img.y = h - s.
  fs = min(w*0.9/wGood, h*0.2). capH = ascent+descent of "GOODFOX" at fs.
  top = max(70, h*0.09); bottom = img.y + s*0.2; lead = capH*1.06; mid = (top+bottom)/2.
  "GOOD" is centred at (w/2, mid - lead/2) and "FOX" at (w/2, mid + lead/2). The lines are stacked, and the fox's ears overlap the bottom of FOX.
- Vertical centring of each word: baseline = y + (actualBoundingBoxAscent - actualBoundingBoxDescent)/2, with textBaseline "alphabetic".

Draw order for each frame:
FRONT (straight onto #view):
  1) fill #29a3f4
  2) draw textA
  3) draw the front fox PNG at (img.x, img.y, s, s) * dpr
  4) draw the HALO
BACK (onto bufB, only when the trail mask has any value > 0.06):
  1) fill #ff0a8c
  2) draw textB
  3) draw the back fox PNG at the same rect
  4) globalCompositeOperation "destination-in", then drawImage(maskCanvas, 0,0,W,H) with smoothing on (a low-res mask upscaled = soft edges), then reset to source-over
  5) draw bufB onto #view
  6) edge glow: globalCompositeOperation "screen", globalAlpha 0.7, draw glowCanvas scaled to W x H, then reset.

HALO (drawn in code because the PNG has none), in device px:
  cx = (img.x + s*0.5)*dpr; cy = (img.y + s*0.046)*dpr + sin(t*1.6)*S*0.006, where S = s*dpr (a gentle bob).
  Ellipse rx = S*0.148, ry = S*0.026.
  Pass 1: lineWidth max(2, S*0.0085); shadowColor rgba(255,214,110,0.95); shadowBlur S*0.03; strokeStyle = horizontal linear gradient #f5c24a → #fff3c2 (0.5) → #f0b93c.
  Pass 2 on the same path: shadowBlur S*0.012; lineWidth max(1, S*0.003); stroke rgba(255,255,240,.9).

==================================================
6. HOVER REVEAL: MOUSE-TRAIL MASK
==================================================
- Keep a Float32Array "field" at 1/5 of the CSS resolution: mw = ceil(w/5), mh = ceil(h/5). Create two canvases of that size: maskCanvas and glowCanvas, each with an ImageData whose RGB is pre-filled with 255.
- Each frame while the pointer is active:
  radius r = min(w,h) * (portrait ? 0.20 : 0.14) * (1 + min(speed,40)/40 * 0.55).
  Stamp Gaussian blobs along the segment from the last stamp position to the current pointer. Spacing is r*0.22, with at most 40 stamps per frame.
  For each stamp, in field units (fx, fy, fr = r/5), for every cell inside the bounding box:
    g = exp(-d² / (fr²*0.42)); skip if g < 0.01; field[i] += g*0.28*(1.05 - field[i]).
  When the pointer is not active, reset the last stamp position to null.
- Each frame decay (k = dt*60): v = v*0.958^k - 0.0035*k, clamped at 0. Track the max value.
- Write alphas:
  mask alpha = smoothstep(0.10, 0.42, v) * 255
  glow alpha = smoothstep(0.02, 0.12, v) * (1 - smoothstep(0.12, 0.38, v)) * 230. This is a bright white fringe only on the soft edge of the reveal.
  Then putImageData to both canvases.
- Result: a soft, blobby, glowing hole follows the cursor and shows the pink/black fox scene underneath. It grows with speed and fades out in about 1 second after the cursor stops.

==================================================
7. TEXT DISINTEGRATE EFFECT (only while hovering)
==================================================
Build on every resize:
- Draw both words in white onto staticA at fs*dpr.
- Pixel cell size cs = max(3, round(fs*dpr/46)).
- Sample staticA alpha at each cell centre on a grid. Every cell with alpha > 110 becomes a particle with home (hx, hy) = cell centre.
- Particle arrays (Float32Array): hx, hy, ox, oy (offset), vx, vy, d (dissolve 0..1), r (seeded random 0..1, mulberry32 seed 7), plus a Uint8Array "on".
- textBox = bounding box of particles, padded by cs*8 horizontally and cs*4 vertically.
- burstR = fs*dpr*(portrait ? 0.5 : 0.42).

Pointer velocity: vx += ((x - px)/k - vx)*0.5 (same for vy), with px = previous x. Units are CSS px per 60fps frame.

Update each frame (k = dt*60, dt capped at 0.05):
- moving = pointer active AND speed > 0.25; sp = min(speed,35)/35; damp = 0.9^k; life = dt/0.85; (nvx, nvy) = normalised velocity.
- For particles within burstR of the pointer (in device px):
    f = 1 - dist/R
    d = min(1, max(d, f*(0.9+sp)))
    imp = f*dpr*(0.35 + sp*1.4); turb = (r-0.5)*2
    vx += (dx/dist)*imp*2.2 + nvx*imp*3.2 + imp*0.8 + turb*imp*2.4
    vy += (dy/dist)*imp*2.2 + nvy*imp*3.2 - imp*0.6 + (rand-0.5)*imp*3
    on = 1
- For every particle that is "on":
    vx += (rand-0.5)*d*0.5*dpr*k
    vy += ((rand-0.5)*0.5 - 0.05)*d*dpr*k
    spring = 0.075*(1-d)²*k
    vx = (vx - ox*spring)*damp; vy = (vy - oy*spring)*damp
    ox += vx*k; oy += vy*k
    d = max(0, d - life)
  When d == 0 and |ox|, |oy| < 0.35 and |vx|, |vy| < 0.08, snap it home and set on = 0. Otherwise add it to the active list.
  The effect: pixels blast away in the direction of the cursor, drift and swirl, then spring back and rebuild the letters in about 0.85s.
- Horizontal slice glitch (only while sweeping fast across the words): if moving, speed > 6, the pointer is inside textBox, and rand < 0.6, push 1–2 slices:
  { y: py + (rand-0.5)*R*1.6, h: cs*(0.5 + rand*3), x: px - R*1.4, w: R*2.8, shift: sign(vx)*cs*(2 + rand*12), life: 0.08 + rand*0.22 s }.
  Remove each slice when its life runs out.

Render text (do this for textA in #ffffff always, and for textB in #fff4f9 only when the mask is visible; both use staticA as the source):
  1) clearRect, then drawImage(staticA)
  2) for every active particle, clearRect its home cell (cs x cs)
  3) for every active particle:
     skip it if d > 0.7 and r > 0.82 (some pixels blink out)
     size sz = cs*(1 - d*(0.55 - 0.45*r))
     stretch = 1 + min(|vx|/dpr*0.35, 3.5)*(d + 0.15)   (horizontal streak)
     globalAlpha = 1 - d*0.5
     fillRect at (hx+ox - sz/2 - (vx<0 ? sz*(stretch-1) : 0), hy+oy - sz/2, sz*stretch, sz)
  4) for every slice: clearRect the strip, then redraw that strip from staticA shifted by "shift" on x.
The letters stay clean and solid until the cursor passes over them.

==================================================
8. INPUT, LOOP, BOOT
==================================================
- Pointer events go on .hero (passive):
  pointermove: set x,y relative to the hero and set active = true. If the pointer was not active before, reset the previous position and the last stamp so nothing jumps.
  pointerdown: same, and always reset.
  pointerleave: active = false.
  pointerup: if pointerType is not "mouse", active = false (touch only reveals while dragging).
- requestAnimationFrame loop: update velocity → updateMask → updateParticles → render textA (and textB if the mask max > 0.06) → compose.
- Boot: Promise.all([load front PNG, load back PNG, fonts.load Anton]) → resize() → add .done to the loader (0.8s fade) → start the loop.

==================================================
9. RESPONSIVENESS CHECKLIST
==================================================
- Full viewport (100svh), no scrolling, 16px minimum gutters (clamp(16px, 2.2vw, 32px)).
- Landscape: GOOD on the left and FOX on the right, behind the centred fox; wings fill the lower sides.
- Portrait (height > width*1.1): GOOD stacked over FOX in the upper half; the fox fills the bottom about 64% of the height and is centred; the UI drops to 10px.
- Touch works through pointer events with touch-action: none.
- Rebuild the layout, particles and mask on resize (debounced 120ms).

Deliver the complete index.html in one file.
[/hidecontent]
← 返回首页