Arctis
aadmin2026-10-26 发布未分类0 阅读

Arctis

类型: · 收录:2026-10-26 23:39:21 · 源码:会员可见

[hidecontent type="vip1"]
Build a single standalone index.html (no build step, no local assets) for a full-screen 3D hero section called "Arctis". All CSS and JS are inline. The 3D scene uses three.js 0.170.0 loaded through an import map from jsDelivr. Every 3D model and image loads directly from the CloudFront URLs below. Do not download or bundle any asset. Must be fully mobile responsive and run smoothly (60fps) on integrated laptop GPUs.

════════════════════════════════════════
1. ASSETS (use these exact URLs)
════════════════════════════════════════
const ASSETS = {
  sky:       'https://d8j0ntlcm91z4.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/hf_20261018_173309_9313c525-cbab-4f3d-bd61-708a49c9927e.png',
  iceTex:    'https://d8j0ntlcm91z4.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/hf_20261018_173040_142ed5cc-1c41-4a58-a5d2-a681a9db6ca8.png',
  crystal:   'https://d8j0ntlcm91z4.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/hf_20261018_173332_c73c2fdb-6236-416b-ae5d-d963288b5723.glb',
  shards:    'https://d8j0ntlcm91z4.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/hf_20261018_182553_eedb8546-454a-4aef-a05b-e199f6100ad6.glb',
  mountainL: 'https://d8j0ntlcm91z4.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/hf_20261018_182548_902b152f-5277-4583-9d7f-3b291c06f148.glb',
  mountainR: 'https://d8j0ntlcm91z4.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/hf_20261018_182551_273c0102-d566-4cf6-8fd5-a4f43f0a7124.glb',
};
- sky: 16:9 night-sky plate (stars, milky way, green aurora curtains top-left and top-right, pink/violet horizon glow at the bottom).
- iceTex: seamless top-down texture of dark blue cracked lake ice.
- crystal: tall faceted bipyramid ice crystal with a cyan swirl baked into its texture.
- shards: ring-shaped cluster of jagged ice spikes.
- mountainL: snowy granite cliff massif (diorama terrain chunk with a flat base slab).
- mountainR: frozen cliff wall with icy waterfalls (also a diorama chunk).
Set crossOrigin 'anonymous' on the TextureLoader and GLTFLoader (the CDN sends Access-Control-Allow-Origin: *).

Import map:
{ "imports": {
  "three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js",
  "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"
} }
Imports: GLTFLoader, RoomEnvironment, Reflector (objects/Reflector.js), EffectComposer, RenderPass, UnrealBloomPass, OutputPass, ShaderPass, FXAAShader.

════════════════════════════════════════
2. HEAD, FONT, TOKENS
════════════════════════════════════════
<title>Arctis — Crafting Interlinked DeFi Frameworks</title>
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="theme-color" content="#06121c">
Font: Google Fonts Montserrat weights 400;500;600;700;800 (display=swap), with preconnect links to fonts.googleapis.com and fonts.gstatic.com.

:root {
  --lime: #c6f432;
  --ink: #ffffff;
  --muted: rgba(255,255,255,.78);
  --line: rgba(255,255,255,.55);
  --pad: clamp(16px, 2.6vw, 36px);
}
* { box-sizing:border-box; margin:0; padding:0 }
html, body { height:100%; background:#040b14; color:var(--ink); overflow:hidden }
body { font-family:"Montserrat", system-ui, sans-serif; -webkit-font-smoothing:antialiased; cursor:none; touch-action:none }
@media (hover:none) { body { cursor:auto } }

════════════════════════════════════════
3. DOM STRUCTURE (in this order)
════════════════════════════════════════
<canvas id="scene" aria-hidden="true"></canvas>   → position:fixed; inset:0; 100%×100%; display:block
<div class="shade"></div>                          → vignette overlay
<div class="ui" id="ui">
  <header>
    <a href="#" aria-label="Arctis home"><svg class="logo" viewBox="0 0 36 30" fill="#fff">
      <path d="M18 0 36 30h-7.6L18 12.4 7.6 30H0z"/>
      <path d="M18 17.5 23.6 27H12.4z" opacity=".95"/></svg></a>
    <nav class="dots" aria-label="Stages"> 5 × <button> (first has class "on"; aria-label "Stage 1".."Stage 5") </nav>
    <div class="right"><button class="menu" aria-label="Open menu">MENU <i><span></span><span></span><span></span></i></button></div>
  </header>
  <section class="copy">
    <div class="phase fade">STAGE 01</div>
    <h1><span class="ln"><span>Crafting</span></span>
        <span class="ln"><span>Interlinked DeFi</span></span>
        <span class="ln"><span>Frameworks</span></span></h1>
    <div class="cta fade">
      <a href="#" class="sq" aria-label="Open"><svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="#fff" stroke-width="1.4"><path d="M2 12 12 2M4.5 2H12v7.5"/></svg></a>
      <a href="#" class="more">+ Read the Docs</a>
    </div>
  </section>
  <div class="hold fade" id="hold" aria-hidden="true">
    <svg viewBox="0 0 66 66"><circle cx="33" cy="33" r="32.5" id="holdRing"/></svg>
    <small>PRESS & HOLD TO ZOOM</small>
  </div>
  <footer class="foot">
    <span class="disc fade">Step into Arctis – A Pioneering, Self-custodial Universe</span>
    <span class="scroll fade">SCROLL TO TRAVERSE</span>
  </footer>
</div>
<div class="cursor" id="cursor"></div>
<div class="loader" id="loader"><div class="box">ARCTIS<div class="bar"><i id="bar"></i></div></div></div>
No sound toggle and no audio of any kind.

════════════════════════════════════════
4. CSS (exact)
════════════════════════════════════════
.shade { position:fixed; inset:0; pointer-events:none; z-index:1;
  background: radial-gradient(120% 90% at 50% 45%, transparent 55%, rgba(2,6,12,.55) 100%),
              linear-gradient(90deg, rgba(3,10,20,.55) 0%, rgba(3,10,20,.15) 32%, transparent 50%); }
.ui { position:fixed; inset:0; z-index:2; pointer-events:none }  .ui a, .ui button { pointer-events:auto }

header { position:absolute; top:0; left:0; right:0; display:flex; align-items:center; justify-content:space-between;
  padding: calc(var(--pad) + 4px) var(--pad) 0 calc(var(--pad) + 2px) }
.logo { width:36px; height:30px; display:block }
.dots { position:absolute; left:50%; top:calc(var(--pad) + 14px); transform:translateX(-50%); display:flex; gap:11px }
.dots button { width:7px; height:7px; border-radius:50%; border:0; background:rgba(255,255,255,.35); cursor:none; transition:background .3s, transform .3s }
.dots button.on { background:#fff; transform:scale(1.25) }
.right { display:flex; align-items:center; gap:24px; font-size:13px; font-weight:500 }
.menu { display:flex; align-items:center; gap:16px; background:none; border:0; color:#fff; font:inherit; letter-spacing:.22em; cursor:none }
.menu i { display:grid; gap:4px; width:19px }  .menu i span { height:2px; background:#fff; display:block }

.copy { position:absolute; left:calc(var(--pad) + 20px); top:50%; transform:translateY(-50%); max-width:560px }
.phase { display:flex; align-items:center; gap:12px; color:var(--lime); font-weight:600; font-size:clamp(14px,1.25vw,17px); letter-spacing:.08em }
.phase::before { content:""; width:30px; height:1.5px; background:var(--lime) }
h1 { margin-top:18px; font-weight:700; font-size:clamp(30px,3.05vw,44px); line-height:1.07; letter-spacing:.005em; text-transform:uppercase; text-shadow:0 2px 30px rgba(0,10,30,.35) }
h1 .ln { display:block; overflow:hidden }
h1 .ln > span { display:inline-block; transform:translateY(110%); transition:transform 1.1s cubic-bezier(.2,.8,.2,1) }
.ready h1 .ln > span { transform:none }
.ready h1 .ln:nth-child(2) > span { transition-delay:.08s }
.ready h1 .ln:nth-child(3) > span { transition-delay:.16s }
.cta { margin-top:32px; display:flex; align-items:center; gap:24px }
.sq { width:40px; height:38px; border:1px solid var(--line); display:grid; place-items:center; transition:background .3s, border-color .3s }
.sq:hover { background:rgba(255,255,255,.12); border-color:#fff }
.more { color:#fff; text-decoration:none; font-size:14.5px; font-weight:500 }  .more:hover { color:var(--lime) }
.fade { opacity:0; transform:translateY(14px); transition:opacity 1s .5s, transform 1s .5s cubic-bezier(.2,.8,.2,1) }
.ready .fade { opacity:1; transform:none }

.foot { position:absolute; left:0; right:0; bottom:0; padding:0 var(--pad) calc(var(--pad) + 2px); display:flex; align-items:flex-end; font-size:11.5px; font-weight:500 }
.foot .disc { color:#fff }
.foot .scroll { position:absolute; left:50%; transform:translateX(-50%); letter-spacing:.24em; font-size:10px; font-weight:600; color:var(--muted) }

.hold { position:absolute; right:calc(var(--pad) + 14px); top:48.5%; width:66px; height:66px; transform:translateY(-50%); border:1px solid rgba(255,255,255,.75); border-radius:50%; display:grid; place-items:center }
.hold::after { content:""; width:5px; height:5px; border-radius:50%; background:#fff }
.hold small { position:absolute; top:calc(100% + 10px); left:50%; transform:translateX(-50%); white-space:nowrap; font-size:10px; letter-spacing:.12em; color:var(--muted); opacity:0; transition:opacity .4s }
.hold:hover small, .hold.show small { opacity:1 }
.hold svg { position:absolute; inset:-1px; width:66px; height:66px; transform:rotate(-90deg) }
.hold circle { fill:none; stroke:var(--lime); stroke-width:1.5; stroke-dasharray:204; stroke-dashoffset:204 }

.cursor { position:fixed; z-index:5; left:0; top:0; width:44px; height:44px; margin:-22px 0 0 -22px; border:1px solid rgba(255,255,255,.7); border-radius:50%; pointer-events:none; transition:width .3s, height .3s, margin .3s, background .3s; will-change:transform }
.cursor::after { content:""; position:absolute; left:50%; top:50%; width:4px; height:4px; margin:-2px; border-radius:50%; background:#fff }
.cursor.big { width:74px; height:74px; margin:-37px 0 0 -37px; background:rgba(255,255,255,.08) }
@media (hover:none) { .cursor { display:none } }
(Do NOT use mix-blend-mode on the cursor. It is expensive over a WebGL canvas.)

.loader { position:fixed; inset:0; z-index:10; background:#030910; display:grid; place-items:center; transition:opacity 1.2s ease, visibility 1.2s }
.loader.done { opacity:0; visibility:hidden }
.loader .box { width:min(260px,70vw); text-align:center; font-size:11px; letter-spacing:.3em; color:var(--muted) }
.loader .bar { margin-top:16px; height:1px; background:rgba(255,255,255,.15); overflow:hidden }
.loader .bar i { display:block; height:100%; width:0; background:var(--lime); transition:width .3s }

Responsive:
@media (max-width:900px) {
  .copy { top:auto; bottom:120px; transform:none; left:var(--pad); right:var(--pad) }
  .hold { display:none }
  .foot { flex-direction:column; align-items:center; gap:14px; text-align:center }
  .foot .scroll { position:static; transform:none; order:-1 }
}
@media (max-width:640px) {
  .right { gap:16px }  .menu { gap:10px; font-size:12px }
  .dots { top:calc(var(--pad) + 62px) }
  .copy { bottom:108px }
  h1 { font-size:clamp(26px,8vw,34px); margin-top:12px }
  .cta { margin-top:22px }
}
@media (max-height:520px) and (orientation:landscape) {
  .copy { top:50%; bottom:auto; transform:translateY(-50%) }  h1 { font-size:24px }  .foot .disc { display:none }
}
@media (prefers-reduced-motion:reduce) { h1 .ln > span, .fade { transition:none } }

════════════════════════════════════════
5. RENDERER, SCENE, LIGHTS
════════════════════════════════════════
isMobile = matchMedia('(max-width: 640px)').matches.
WebGLRenderer({ canvas, antialias:false, stencil:false, powerPreference:'high-performance' }).
MAX_DPR = min(devicePixelRatio, isMobile ? 1.25 : 1.5), MIN_DPR = 0.7, start at MAX_DPR.
toneMapping ACESFilmic, exposure 1.05, outputColorSpace SRGB.
scene.background = #06121c; scene.fog = FogExp2(0x22304a, 0.0068).
Environment: PMREMGenerator.fromScene(new RoomEnvironment(), 0.04); scene.environmentIntensity = 0.35.
Camera: PerspectiveCamera(42, aspect, 0.1, 600). FOV is set per aspect: <0.8 → 58, <1.2 → 50, else 40.
LoadingManager: onProgress sets #bar width to loaded/total %.

Lights:
- HemisphereLight(0x9cc2ee, 0x101c2c, 0.9)
- DirectionalLight "moon" 0xbcd6ff, 1.9 at (-25, 35, 40)
- DirectionalLight "rim" 0xff9ec4, 0.35 at (10, 40, -60) (pink horizon bounce)
- PointLight "coreLight" 0x37d4ff, 60, distance 40, decay 1.6 (y = crystal centre)
- PointLight "baseLight" 0x4fe3ff, 70, distance 30, decay 1.8 at (0, 0.8, 0)

Reflection layer: REFLECT = 1; helper reflect(obj) enables layer 1 on every descendant.

Helper fit(obj, height): updateMatrixWorld(true), measure the Box3, multiply the scale by height/size.y, update again, re-measure, centre x/z, set the bottom to y=0, then wrap in a new Group and return the wrapper. Always pass src.clone() when placing copies.

Helper radialTexture(inner, outer): 256px canvas radial gradient (stop 0 inner, stop .25 inner at alpha .55, stop 1 outer) → CanvasTexture, SRGB.

════════════════════════════════════════
6. SCENE CONTENT
════════════════════════════════════════
SKY: CylinderGeometry(220, 220, 130, 64, 1, openEnded true, thetaStart π*0.72, thetaLength π*0.56), MeshBasicMaterial({ map: sky (SRGB, anisotropy 4), side: BackSide, fog:false, depthWrite:false }), position (0, 50, 40), renderOrder -1, inside a skyGroup. fitSky(): scale.y = aspect<0.8 ? 1.7 : aspect<1.2 ? 1.3 : 1; position.y = -15 + 65*scale.y. Call it on load and on resize. Put the sky on the reflect layer.

FROZEN LAKE (size 400):
- Reflector(PlaneGeometry(400,400), { textureWidth: innerWidth*(isMobile?0.25:0.35), textureHeight: innerHeight*(isMobile?0.25:0.35), color: 0x56697d }), rotated -π/2 on x, y = -0.02. Set mirror.camera.layers.set(REFLECT) so it reflects only the sky, crystal, shard ring and the two near cliffs. Wrap its onBeforeRender so it only re-renders every other frame.
- Overlay plane 400×400 with MeshStandardMaterial({ map: iceTex (SRGB, RepeatWrapping, repeat 26×26, anisotropy 4), color 0x9fc4e4, roughness .42, metalness .05, transparent, opacity .62, depthWrite:false }), y = 0.01.
- Cyan light pool: PlaneGeometry(26,26) flat on the ice at y .05, MeshBasicMaterial with radialTexture('rgba(90,230,255,1)','rgba(20,120,255,0)'), additive blending, opacity .45, depthWrite false.

MOUNTAINS: load mountainL (L) and mountainR (R). On every mesh: roughness .92, metalness 0, envMapIntensity .25, color = 0x8ea3bd (moonlit tint).
place(src, h, x, z, ry, sx, sz) → fit(src.clone(), h), position (x, -h*0.17, z) (sinks the diorama slab under the ice), rotation.y = ry, scale (sx, 1, sz), add to scene.
  reflect(place(L, 33, -46, -38, -0.5, 1.15, 1.15))   // left massif
  place(R, 21, -72, -66, -0.5, 1.3, 1.3)              // left back ridge
  reflect(place(R, 33, 42, -32, -2.1, 1.15, 1.15))   // right frozen cliff wall
  place(L, 26, 70, -66, 2.4, 1.3, 1.3)                // right back peak
  place(L, 16, -16, -118, 0.3, 1.6, 1.2)              // distant valley peak (fog-hazed)
  place(R, 17, 20, -128, -1.2, 1.6, 1.2)              // distant valley peak

HERO CRYSTAL (group "hero" at origin):
- Load the crystal GLB. Take its baked map (SRGB). For each mesh, geometry = toNonIndexed() then computeVertexNormals() for crisp facets.
- Material: MeshPhysicalMaterial({ color 0xd8f4ff, map baked, emissive 0x38c8ff, emissiveMap baked, emissiveIntensity .75, metalness 0, roughness .04, clearcoat 1, clearcoatRoughness .03, specularIntensity 1, envMapIntensity 1.8, transparent:true, opacity .9, flatShading:true }). NO transmission: transmission re-renders the whole scene and kills performance.
- fit(crystal, 17.5), then crystal.position.y = 1.9. Add it to hero and the reflect layer.
- Energy core: 3 crossed planes (PlaneGeometry(h*0.34, h*0.62), rotated 0, 60, 120° on y, centred at the crystal centre y-0.3, renderOrder 10) using an additive ShaderMaterial (transparent, depthWrite false, depthTest false, uniform uTime):
  vertex: pass uv.
  fragment:
    float h(vec2 p){return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453);}
    float n(vec2 p){vec2 i=floor(p),f=fract(p);f=f*f*(3.-2.*f);return mix(mix(h(i),h(i+vec2(1,0)),f.x),mix(h(i+vec2(0,1)),h(i+vec2(1,1)),f.x),f.y);}
    float fbm(vec2 p){float v=0.,a=.5;for(int i=0;i<5;i++){v+=a*n(p);p*=2.03;a*=.5;}return v;}
    void main(){ vec2 p=vUv-.5; float t=uTime*.35;
      float s=sin(p.y*9.-t*2.)*.12; float d=abs(p.x - s*(1.-abs(p.y)*1.6));
      float f=fbm(vec2(p.x*6.+t, p.y*5.-t*1.3));
      float strand=smoothstep(.09,.0,d-f*.06)*smoothstep(.5,.15,abs(p.y));
      float wisps=pow(fbm(p*7.+vec2(t,-t)),3.)*1.4*smoothstep(.45,.1,length(p*vec2(1.6,1.)));
      float core=smoothstep(.16,.0,length(p*vec2(1.,1.3)));
      float a=strand*.8+wisps*.9+core*.85;
      vec3 col=mix(vec3(.15,.6,1.),vec3(.75,1.,1.),clamp(core+strand*.5,0.,1.));
      gl_FragColor=vec4(col*a,a); }
- Halo: Sprite with radialTexture('rgba(150,240,255,1)','rgba(40,140,255,0)'), additive, opacity .4, depthTest false, renderOrder 11, scale = crystal height*0.55, at crystal centre y-0.4.
- Set coreLight.y to the crystal centre.

SHARD RING: load the shards GLB. Every mesh gets MeshStandardMaterial({ color 0x7cc8f4, map (SRGB), emissive 0x1aa6ff, emissiveMap map, emissiveIntensity .7, roughness .1, metalness .05, envMapIntensity 1.6 }) (opaque, no transmission). Keep the materials in shardMats[]. ring = fit(g, 5.2), then scale x and z by 1.15. Add it and put it on the reflect layer. Add 5 loose clones: fit(g.clone(), 1 + rand*1.3), random angle, radius 11–25, position (cos*r, -0.4, sin*r*0.6 + 2), random y rotation, scale ×0.6.

ICE DUST: Points, count isMobile ? 600 : 1400. Positions x ±45, y 0–40, z ±40 then -5. Per-point attribute "seed" in 0–1. Additive ShaderMaterial (depthWrite false), uniforms uTime and uPR (pixel ratio).
  vertex: p.y = mod(p.y - uTime*(0.4+seed*0.6), 40.); p.x += sin(uTime*.3+seed*20.)*1.5; gl_PointSize = (1.2+seed*2.6)*uPR*(30./-mv.z); vA = .25+seed*.6
  fragment: soft round dot, colour vec3(.75,.92,1.), alpha smoothstep(.5,0,dist)*vA

════════════════════════════════════════
7. POST-PROCESSING
════════════════════════════════════════
EffectComposer(renderer) with default targets (NO MSAA samples; MSAA doubled the frame cost on integrated GPUs).
Passes: RenderPass → UnrealBloomPass(resolution, strength .6, radius .55, threshold .86), with its setSize overridden to run at HALF resolution → OutputPass → ShaderPass(FXAAShader) last. FXAA resolution uniform = (1/(innerWidth*dpr), 1/(innerHeight*dpr)); update it on resize and on DPR change.

════════════════════════════════════════
8. CAMERA, INTERACTION, ANIMATION
════════════════════════════════════════
rig = { zoom:0, zoomTarget:0, hold:0, mx:0, my:0, tx:0, ty:0 }
Camera path: FAR = pos (0, 5.4, 46), look (0, 8.2, 0). NEAR = pos (1.5, 6.2, 17), look (0, 8.6, 0).
- pointermove: tx/ty = normalised -1..1; move #cursor via transform translate3d(clientX, clientY, 0).
- wheel (passive): zoomTarget += deltaY*0.0009, clamp 0..1.
- touch: vertical swipe changes zoomTarget by (prevY - y)*0.0025 (clamp 0..1); horizontal finger position sets tx.
- Press & hold (mouse pointerdown not on an a/button): holding = true and the cursor gets class "big". pointerup releases. Hovering any a/button also toggles "big".
- Pagination dots switch camera shots: SHOTS = [{yaw:0,zoom:0},{yaw:.55,zoom:.45},{yaw:-.6,zoom:.35},{yaw:.25,zoom:.85},{yaw:-.25,zoom:.65}]. Clicking dot i sets the "on" class, yawTarget and zoomTarget.

Render loop (requestAnimationFrame, dt = min(clock delta, .05)). All easing is frame-rate independent: k(rate) = 1 - exp(-rate*dt).
  mx,my → tx,ty at k(2.8); hold → (holding?1:0) at k(holding?1.5:3.6); zoom → zoomTarget at k(3); yaw → yawTarget at k(2.4).
  Hold ring: strokeDashoffset = round(204*(1-hold)). Only write to the DOM when the value changes.
  z = clamp(zoom + hold*0.55, 0, 1); ez = smoothstep curve z*z*(3-2z); lerp the position and look target FAR→NEAR by ez.
  Portrait (aspect < 0.8): pos.z += 16*(1-ez), pos.y += 1.5*(1-ez), look.y -= 3.2*(1-ez).
  ang = yaw + mx*0.12; camera.position = (sin(ang)*pos.z + pos.x, pos.y - my*1.4, cos(ang)*pos.z); lookAt(look.x + mx*1.2, look.y - my*0.6, look.z).
  skyGroup.rotation.y = -mx*0.03 + yaw*0.6 (parallax depth).
  Unless reduced motion: hero.rotation.y = t*0.18; hero.position.y = sin(t*0.8)*0.25 (slow spin + float).
  pulse = .5 + .5*sin(t*1.6); coreLight = 55 + pulse*25 + hold*60; baseLight = 60 + pulse*25; bloom.strength = .7 + hold*.5; shard emissiveIntensity = .55 + pulse*.3; update the uTime uniforms.
  composer.render(dt).
  Adaptive resolution: track the average rAF interval (EMA 0.05). After 3s, every 60 frames: if avg > 22ms and dpr > MIN, lower dpr by .15; else if avg < 17.5ms and dpr < MAX, raise it by .1. Apply to renderer, composer, FXAA and the dust uPR.

Resize: update FOV/aspect, fitSky, renderer.setSize, composer.setSize, FXAA uniform.

Startup: build the dust immediately. Promise.allSettled([sky, ice, mountains, crystal, shards]), then add "done" to #loader, "ready" to #ui (triggers the headline line-reveal and the .fade elements), add "show" to #hold at 2.2s and remove it at 6s.

════════════════════════════════════════
9. EXPECTED LOOK
════════════════════════════════════════
Wide cinematic night shot. The green aurora fills the top of the frame over a starry sky with a pink-violet horizon. Dark snowy granite cliffs frame the left, a tall frozen-waterfall cliff wall frames the right, and fog-hazed peaks sit low in the centre valley. A large glassy ice crystal with a glowing cyan swirl core floats at centre, slowly spinning and bobbing. Its tip sits in a glowing ring of ice spikes on a reflective, cracked black-blue frozen lake that mirrors the crystal and a cyan light pool. Fine ice dust drifts down. Text is on the left (lime "STAGE 01", white uppercase Montserrat headline "CRAFTING / INTERLINKED DEFI / FRAMEWORKS"), the press-and-hold ring is on the right, the footer sits at the bottom, and a custom ring cursor follows the mouse. On phones the headline block moves to the bottom, the crystal sits above it, and the footer lines stack and centre.
[/hidecontent]
← 返回首页