Wealthcore
aadmin2026-09-03 发布Site模板10 阅读

Wealthcore

类型:Site模板 · 分类:未分类 · 收录:2026-09-03 19:57:57 · 源码:会员可见

[hidecontent type="vip1"]
Build a pixel-faithful recreation of the "Meridial" landing page — a dark cinematic
fintech hero plus a dashboard section, joined by a gesture-triggered morph.
Output index.html, css/style.css, js/main.js.

═══════════════════════════════════════════════════════════════════════════
0. NON-NEGOTIABLE ARCHITECTURE
═══════════════════════════════════════════════════════════════════════════
• The page is EXACTLY ONE SCREEN and NEVER SCROLLS. body{overflow:hidden}.
• Every length is ARTBOARD UNITS × a single scale --u. No fixed px anywhere
  except --u-max and --shell-max. Pattern: calc(<number> * var(--u)).
• Structure uses fractions and auto margins — never fixed offsets.
• BOTH sections are absolutely positioned on the SAME screen, one visible at a
  time. Section 2 is NOT below section 1.

═══════════════════════════════════════════════════════════════════════════
1. ASSETS — exact URLs
═══════════════════════════════════════════════════════════════════════════
VIDEO (1080x1916, 10.04s, seamless loop, silent) — used by BOTH sections:
https://d8j0ntlcm91z4.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/hf_20260827_142319_f711d024-2a01-47c3-93d9-1224b1496276.mp4

POSTER / CSS BACKGROUND FALLBACK (9:16 still = frame 0):
https://d2ol7oe51mr4n9.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/9df0fbe6-c369-4a24-b0cc-25d538dbb30b.png

LOGOS (transparent PNG, 10% transparent margin), prefix
https://d8j0ntlcm91z4.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/ :
intel  1228x636  hf_20260827_140033_f85a881b-6d29-4cae-9c41-c2165f10423e.png
google 1228x540  hf_20260827_140037_4c53cf5c-7e8f-4b73-9844-6ed86f42f123.png
sony   1228x385  hf_20260827_140041_4f5d4a4f-ade9-41f6-8f16-d02b958ecc4e.png
amazon 1228x524  hf_20260827_140044_8dd26bc3-e7b5-4297-86e3-98bd5657a480.png
adobe  1228x475  hf_20260827_140048_263047b9-3ae4-4018-b678-aefebdbb94be.png

FONTS — Google Fonts: Geist 400/500; DM Sans 500 (radar months + chips ONLY).
--font-sans: 'Suisse Intl','Geist','Helvetica Neue',Arial,sans-serif;
Do NOT add a Suisse @font-face.

═══════════════════════════════════════════════════════════════════════════
2. TOKENS (:root)
═══════════════════════════════════════════════════════════════════════════
--artboard-w:1606; --artboard-h:1161; --column-content:1037; --column-panel:569;
--u-max:1.35px;
--u: min(calc(100vh/var(--artboard-h)), calc(100vw/var(--artboard-w)), var(--u-max));
--split-panel: calc(var(--column-panel)/var(--artboard-w));
--shell-max:2560px;
--morph:1; --morph-dur:1000ms; --morph-ease:cubic-bezier(0.33,0,0.2,1);
--gutter:calc(35*var(--u)); --hairline:max(1px, calc(1*var(--u)));
--surface-top:#06080A; --surface-bottom:#191919; --white:#fff;
--text-muted:#c1c1c1; --ink:#111; --ink-strong:#000;
--radius-card:calc(25*var(--u)); --radius-control:calc(3*var(--u));
--radius-glow:calc(10*var(--u));
--track-tight:-0.04em; --track-snug:-0.03em; --track-narrow:-0.02em; --track-wide:0.07em;
Base: border-box everywhere; body bg var(--surface-bottom), colour #fff,
antialiased, text-rendering geometricPrecision; p/h1/ul margin 0; ul no list-style;
a colour inherit; img,svg display block.

═══════════════════════════════════════════════════════════════════════════
3. PAGE SHELL + GLOW/VIDEO LAYER
═══════════════════════════════════════════════════════════════════════════
.page{position:relative; block-size:100svh; overflow:hidden}
.section--hero,.section--signal{position:absolute; inset:0; block-size:100svh}
.section--signal{visibility:hidden}
.signal-shown .section--signal{visibility:visible}
.signal-shown .section--hero{visibility:hidden}
.section--hero{display:flex; min-block-size:100svh; overflow:hidden;
  background:linear-gradient(180deg,var(--surface-top),var(--surface-bottom))}
.hero__grid{position:relative; z-index:1; display:grid;
  grid-template-columns:1fr calc(var(--split-panel)*100%);
  inline-size:100%; max-inline-size:var(--shell-max); margin-inline:auto}
.hero__content,.hero__panel{min-inline-size:0}
.hero__content{display:flex; flex-direction:column;
  padding:calc(46*var(--u)) var(--gutter) calc(59*var(--u))}
.hero__panel{position:relative; display:flex; flex-direction:column;
  justify-content:center; padding-block:calc(46*var(--u)) calc(59*var(--u))}
.hero__glow{position:absolute; inset-block:0; right:0;
  left:calc(50% + min(100%,var(--shell-max))/2 - min(100%,var(--shell-max))*var(--split-panel));
  border-radius:var(--radius-glow) 0 0 var(--radius-glow);
  background:#fff url(<POSTER>) center/cover no-repeat;
  filter:grayscale(1); overflow:hidden}
Both .hero__glow AND .signal__wash contain:
<video class="glow__video" autoplay muted loop playsinline preload="auto"
       poster="<POSTER>"><source src="<VIDEO>" type="video/mp4"></video>
.glow__video{position:absolute; inset:0; width:100%; height:100%;
  object-fit:cover; border-radius:inherit; pointer-events:none}

═══════════════════════════════════════════════════════════════════════════
4. MASTHEAD (identical markup in BOTH sections)
═══════════════════════════════════════════════════════════════════════════
.masthead{flex:none; display:grid; grid-template-columns:1fr auto 1fr;
  align-items:center; gap:calc(24*var(--u))}
Brand = inline SVG (viewBox "0 0 29 24", 12 paths in <g transform="translate(29 24)">,
fill = vertical linearGradient #FFFFFF→#999999, UNIQUE gradient id per section)
+ wordmark "Meridial". gap 9u, mark 29x24u.
.brand__name{font-size:calc(23.12195*var(--u)); line-height:.99473; --track-tight}
Nav (white-space:pre): "Platform ", " Compliance ", "Security", "Company".
gap 44u, 16u/1.1875/--track-snug, opacity .5→1 over .2s.
.btn-nav{justify-self:end; padding:calc(10.37*var(--u)) calc(15.83*var(--u));
  border-radius:var(--radius-control); background:#fff; color:var(--ink);
  16u/--track-snug; hover #e6e6e6}
Burger hidden on desktop.

═══════════════════════════════════════════════════════════════════════════
5. HERO BODY
═══════════════════════════════════════════════════════════════════════════
.hero__body{inline-size:min(calc(842*var(--u)),100%); margin-block:auto;
  margin-inline:auto; text-align:center}
.hero__head{margin-bottom:calc(54*var(--u))}
PILL "Managing $4.2B+ in tracked assets" — SQUARE corners, width 279u, padding 10u,
text-align left, 12u/1.05/--track-wide/uppercase, background
linear-gradient(90deg, rgba(153,153,153,.1), rgba(255,255,255,.2) 45.55%, rgba(153,153,153,.1));
::before inset hairline via padding + mask-composite:exclude, background
linear-gradient(90deg, rgba(152,152,152,.1), rgba(102,102,102,.1)).
TITLE — three <span class="hero__line">: "Clarity, across " / "every portfolio you" / "manage"
.hero__title{inline-size:min(calc((842 - 96*0.04)*var(--u)),100%);
  margin:calc(45*var(--u)) auto 0; font-size:calc(96*var(--u)); font-weight:400;
  line-height:1; --track-tight}
Gradient sits on EACH LINE (a line that animates opacity gets its own stacking
context and cannot show through an ancestor's background-clip:text):
.hero__line{display:block; background-image:linear-gradient(180deg,#fff,#999);
  background-size:100% 300%; background-clip:text; color:transparent}
:nth-child(1){background-position:0 0%} (2){0 50%} (3){0 100%}
SUB "Meridial gives advisors real-time insight across every client's holdings —
without the spreadsheet" — min(calc((472-20*0.04)*var(--u)),100%),
20u/1.4/--track-tight/var(--text-muted).
ACTIONS "Request access"(solid) + "Book a demo"(ghost) — gap 18u, width 372u,
margin-top 54u. .btn 177x62u, radius --radius-control, 18u/--track-narrow.
.btn--solid{background:#fff;color:#000;hover #e6e6e6}
.btn--ghost{border:var(--hairline) solid rgba(255,255,255,.1); hover .35}

═══════════════════════════════════════════════════════════════════════════
6. MARQUEE
═══════════════════════════════════════════════════════════════════════════
.trusted{flex:none; inline-size:min(calc(645*var(--u)),100%); margin-inline:auto}
.trusted__label "Trusted by wealth advisors at" — min(calc((227+12*0.07)*var(--u)),100%),
12u/1.08333/--track-wide/uppercase/centred/nowrap/opacity .4.
.marquee{inline-size:min(calc(606*var(--u)),100%); height:calc(25.351*var(--u));
  margin:calc(44*var(--u)) auto 0; overflow:hidden; opacity:.4;
  mask-image:linear-gradient(90deg,transparent 0%,#000 18%,#000 82%,transparent 100%)}
.marquee__track{--marquee-shift:calc(-644.104*var(--u)); display:flex;
  align-items:center; gap:calc(49*var(--u)); width:max-content; height:100%;
  animation:marquee-scroll 32s linear infinite}
.marquee:hover .marquee__track{animation-play-state:paused}
@keyframes marquee-scroll{from{transform:translate3d(0,0,0)}
  to{transform:translate3d(var(--marquee-shift),0,0)}}
Five logos TWICE (second set alt="" aria-hidden="true").
PADDING COMPENSATION (each PNG has a 10% transparent margin per side):
.marquee__logo{flex:none; object-fit:contain}
--intel {62.377 x 32.301u; margin-inline:calc(-5.181*var(--u))}
--google{92.574 x 40.743u; margin-inline:calc(-7.689*var(--u))}
--sony  {115.125 x 36.160u; margin-inline:calc(-9.563*var(--u))}
--amazon{96.472 x 39.547u; margin-inline:calc(-8.013*var(--u))}
--adobe {112.065 x 43.409u; margin-inline:calc(-9.308*var(--u))}

═══════════════════════════════════════════════════════════════════════════
7. HERO PANEL — two glass cards
═══════════════════════════════════════════════════════════════════════════
.panel{position:relative; inline-size:min(calc(328*var(--u)),100%); margin-inline:auto}
.card{position:relative; width:100%; border-radius:var(--radius-card);
  background:rgba(0,0,0,0.325); backdrop-filter:blur(calc(77.65*var(--u)))}
.card::before — inset hairline, mask-composite:exclude,
  linear-gradient(127.6deg, rgba(0,0,0,.1) 4.14%, rgba(102,102,102,.1) 54.67%)
.card--allocation{height:calc(386*var(--u))}
.card--value{height:calc(264*var(--u)); margin-top:calc(11*var(--u))}
.card__item{position:absolute; left:calc(var(--x)*var(--u)); top:calc(var(--y)*var(--u)); margin:0}
.fig-box{inline-size:calc(var(--w)*var(--u)); text-align:center; white-space:nowrap}
CARD 1 — (--x,--y[,--w]) + inline reveal delay/dur:
 dot filled 28,29 2000/300 | label "Target allocation" 63,34 2000/300
 tl-line 38.5,55 --len:185 2133/333
 stat "60%" 63,78 w72 · "/" 161,78 w13 (--o:.3) · "40%" 200,78 w72 — all 2383/100
 unit "equity" 63,120 w38 · "bonds" 200,120 w40 — 2467/50
 pill--card "Rebalance needed" 63,166 2550/50
 note "Bond allocation drifted 4.2%" 63,203 (--o:.4) 2683/183
 dot filled 28,247 + label "+7.2% YTD" 63,252 — 2117/417
 tl-line dim 38.5,274 --len:12 (--o:.2) 2367/417
 dot hollow 28,290 + label dim "next review: Q3" 63,295 — 2367/417
 tl-line dim 38.5,316 --len:12 2600/383
 dot hollow 28,333 + label dim "next review: Q4" 63,338 — 2600/383
CARD 2:
 label "Whitmore Family Trust" 28,29 2317/350
 value "$18,420,500" 28,70 w178 (32u) 2533/350
 delta "▲ 2.4%" 216,79 w50 (14u) 2533/350
 chart-grid svg 28,121 viewBox"0 0 274 120" preserveAspectRatio=none, --o:.45 2667/367
 chart svg 19,132 viewBox"0 0 290 117", overflow visible 2667/367
   Two paths on ONE bezier spline: fill linearGradient #D9D9D9@.1→#737373@0,
   stroke #FFFFFF@.6 width 1 round cap/join. A MULTI-PEAK trend, not a
   monotonic rising line.
Sub-elements: .dot 22x22u radius 50%, ::after 6x6u white at 8,8;
 --filled bg rgba(255,255,255,.11); --hollow border hairline rgba(255,255,255,.1),
 ::after offset -hairline, opacity .23.
.tl-line{width:var(--hairline); height:calc(var(--len)*var(--u));
 background-image:repeating-linear-gradient(to bottom,#fff 0 calc(2*var(--u)),
 transparent calc(2*var(--u)) calc(8*var(--u)))}
.card__label 12u/1.05/--track-wide/uppercase/nowrap; --dim{--o:.2}
.stat 32u/1/--track-narrow; .stat--sep{--o:.3}
.stat__unit 14u/1/--track-narrow  ← NO white-space:nowrap (it must be able to wrap)
.card__note 14u{--o:.4}; .pill--card{width:152u; padding:7u 8u}
.panel__title "Real-time drift detection" margin-top 96u, 28u, weight 500,
 --track-tight, centred, nowrap
.panel__caption "From target to rebalance — tracked automatically" margin-top 16u,
 16u/1.375/--track-tight/var(--text-muted)

═══════════════════════════════════════════════════════════════════════════
8. LOAD REVEAL
═══════════════════════════════════════════════════════════════════════════
<script>document.documentElement.className+=' js'</script> BEFORE the stylesheet.
.js [data-reveal]:not(.is-revealed){opacity:0; will-change:opacity}
.is-ready [data-reveal]:not(.is-revealed){
  animation:reveal calc(var(--rv-dur,600ms)*var(--rv-scale,1)) linear
            calc(var(--rv-delay,0ms)*var(--rv-scale,1)) both}
@keyframes reveal{from{opacity:0} to{opacity:var(--o,1)}}  ← target --o, NOT 1
LINEAR ramp, OPACITY ONLY.
masthead 70/615 · glow 90/970 · title-1 267/483 · title-2 417/450 ·
title-3 567/467 · sub 833/617 · actions 833/600 · marquee 850/600 ·
eyebrow 1017/50 · panel-note 1675/760 · card-1 1783/350 · card-2 1867/350
JS adds .is-revealed on animationend.

╔═════════════════════════════════════════════════════════════════════════╗
║ 9. SECTION TWO — "SIGNAL"                                               ║
╚═════════════════════════════════════════════════════════════════════════╝
9.1 SHELL
.section--signal{display:flex; align-items:center; min-block-size:100svh;
  overflow:hidden; background:var(--surface-bottom)}
.signal{position:relative; inline-size:100%; max-inline-size:var(--shell-max);
  block-size:calc(var(--artboard-h)*var(--u)); margin-inline:auto}
.signal__wash{position:absolute; inset:0; overflow:hidden;
  background:#fff url(<POSTER>) center/cover no-repeat; filter:grayscale(1)}
  + its own <video class="glow__video"> child. FULL strength.

9.2 MASTHEAD
.signal__masthead{position:absolute; left:calc(34*var(--u)); right:calc(43.34*var(--u));
  top:calc(46*var(--u) - max(0px,(100svh - var(--artboard-h)*var(--u))/2))}
  ← the inset subtraction is REQUIRED: the 1161u shell centres, so below aspect
    1606/1161 it sits inset from the top and the nav would step down at the swap.

9.3 BOARD
.board{position:absolute; top:calc(236*var(--u)); left:0; right:0; margin-inline:auto;
  inline-size:calc(1198*var(--u)); block-size:calc(689*var(--u)); display:flex;
  padding:calc(13*var(--u)) calc(20*var(--u)) calc(15*var(--u)) calc(14*var(--u));
  border-radius:calc(39*var(--u)); background:none}
  ← FIXED in units, never stretched: the cards must stay the same size as the
    hero panel's so the travel is translate-only.
.board__col{display:flex; flex-direction:column; min-inline-size:0}
.board__col--allocation{flex:none; inline-size:calc(328*var(--u)); gap:calc(11*var(--u))}
.board__col--mid{flex:none; inline-size:calc(407*var(--u)); gap:calc(12*var(--u));
  margin-inline-start:calc(13*var(--u))}
.board__col--end{flex:none; inline-size:calc(407*var(--u)); gap:calc(12*var(--u));
  margin-inline-start:calc(9*var(--u))}
EVERY COLUMN SUMS TO 661u:
  col1 386+11+264 · col2 264+12+385 · col3 264+12+385
.board-card{position:relative; flex:none; border-radius:calc(25*var(--u));
  background:rgba(0,0,0,0.325); backdrop-filter:blur(calc(77.65*var(--u)))}
.board-card::before — identical inset hairline to .card::before.
.bc__item{position:absolute; left:calc(var(--x)*var(--u)); top:calc(var(--y)*var(--u)); margin:0}
.bc__item--end{left:auto; right:calc(var(--x)*var(--u))}
.board-card .card__note--bright{opacity:1}
.board-card .trend{inline-size:calc(290*var(--u)); block-size:calc(117*var(--u));
  overflow:visible}   ← 290 not 260: it is the hero's card arriving.

9.4 COLUMN 1 (328u) — the two hero cards, .bc__item, NO --rv-delay
(they arrive by travelling, not by revealing).

9.5 COLUMN 2, CARD "composition" (407 × 264u)
  "PORTFOLIO COMPOSITION" 28,29
  stat "58%" 28,75 · "34%" 161,75 · "8%" 292,75
  unit "Equities" 28,118 · "Bonds" 161,118 · "Cash & alt" 292,118
  <div class="bars" style="--y:161"> → 3 bars --w:198.63 | 105.22 | 28.52
  "LAST REBALANCED: 12 DAYS AGO" 28,227
.bars{position:absolute; top:calc(var(--y)*var(--u)); left:calc(18.02*var(--u));
  right:calc(45.8*var(--u)); display:flex; block-size:calc(37*var(--u))}
.bars__bar{flex:var(--w) 1 0; background:#fff}
.bars__bar:nth-child(2){opacity:.4}  :nth-child(3){opacity:.2}
.bars__bar + .bars__bar{margin-inline-start:calc(5.9*var(--u))}
.bars__bar:last-child{margin-inline-start:calc(4.91*var(--u))}

╔═════════════════════════════════════════════════════════════════════════╗
║ 9.6 CARD "ADVISOR ACTIVITY" — USE THIS CODE VERBATIM. DO NOT REDESIGN.  ║
║                                                                         ║
║ This card has failed twice. The cause both times: a competing           ║
║ `.radar{position:absolute; left:…}` rule overriding the right-anchor,   ║
║ which slides the radar over the figure column.                          ║
║                                                                         ║
║ THE GEOMETRY: the 407u card is two bands that TILE EXACTLY at x=122u.   ║
║   left band  28u → 122u   (a 94u text column: 28 + 94 = 122)            ║
║   right band 122u → 397u  (the 275u radar: 407 − 10 − 275 = 122)        ║
║   overlap MUST be exactly 0.                                            ║
║                                                                         ║
║ RULES:                                                                  ║
║  1. The radar is anchored with `right`, NEVER `left`.                   ║
║  2. Do NOT write any other rule that sets `left` on .radar.             ║
║  3. `.bc__stack` caps the two long labels at 94u so they WRAP.          ║
║  4. `.stat__unit` must NOT carry white-space:nowrap.                    ║
║  5. "Risk profile" is short and takes NO .bc__stack.                    ║
╚═════════════════════════════════════════════════════════════════════════╝

── HTML ──────────────────────────────────────────────────────────────────
<article class="board-card board-card--activity">
  <p class="bc__item card__label" style="--x:28; --y:26">Advisor activity</p>
  <p class="bc__item card__label card__label--dim bc__item--end"
     style="--x:29; --y:26">full history</p>

  <p class="bc__item stat"       style="--x:28; --y:69">218</p>
  <p class="bc__item stat__unit bc__stack" style="--x:28; --y:113">Portfolios reviewed</p>
  <p class="bc__item stat"       style="--x:28; --y:176">35</p>
  <p class="bc__item stat__unit bc__stack" style="--x:28; --y:220">Rebalances triggered</p>
  <p class="bc__item stat"       style="--x:31; --y:284">74</p>
  <p class="bc__item stat__unit" style="--x:28; --y:328">Risk profile</p>

  <div class="radar">
    <svg viewBox="0 0 275 250" aria-hidden="true" focusable="false">
      <g class="radar__rings" transform="translate(35.5 25)">
        <path d="M 100 0 L 178.183 37.651 L 197.493 122.252 L 143.388 190.097 L 56.612 190.097 L 2.507 122.252 L 21.817 37.651 Z"/>
        <path transform="translate(20 20)" d="M 80 0 L 142.547 30.121 L 157.994 97.802 L 114.711 152.078 L 45.289 152.078 L 2.006 97.802 L 17.453 30.121 Z"/>
        <path transform="translate(40 40)" d="M 60 0 L 106.910 22.591 L 118.496 73.351 L 86.033 114.058 L 33.967 114.058 L 1.504 73.351 L 13.090 22.591 Z"/>
        <path transform="translate(60 60)" d="M 40 0 L 71.273 15.060 L 78.997 48.901 L 57.355 76.039 L 22.645 76.039 L 1.003 48.901 L 8.727 15.060 Z"/>
        <path transform="translate(81 80)" d="M 20 0 L 35.637 7.530 L 39.499 24.450 L 28.678 38.019 L 11.322 38.019 L 0.501 24.450 L 4.363 7.530 Z"/>
      </g>
      <path class="radar__area2" transform="translate(75 46)"
            d="M 60.5 0 L 0 28.5 L 40.649 90.273 L 51.567 105.149 L 78.366 123 L 123.030 100.190 L 123.030 28.5 Z"/>
      <g transform="translate(39 91)">
        <path class="radar__area1"
              d="M 62.876 14.986 L 0 55 L 78.449 76.525 L 131.5 121 L 115.339 45.957 L 127.218 14.986 L 95.542 0 Z"/>
      </g>
      <g class="radar__months">
        <text x="136.0" y="14">Jan</text>
        <text x="231.5" y="66">Feb</text>
        <text x="251.0" y="151">Mar</text>
        <text x="192.5" y="232">Apr</text>
        <text x="81.0"  y="232">May</text>
        <text x="15.5"  y="149">Jun</text>
        <text x="39.5"  y="62">Jul</text>
      </g>
    </svg>
    <ul class="radar__chips">
      <li style="--y:0">218</li>
      <li style="--y:32">35</li>
      <li style="--y:64">74</li>
    </ul>
  </div>
</article>

── CSS ───────────────────────────────────────────────────────────────────
.board-card--activity{ block-size: calc(385 * var(--u)); }

/* The 94u text column. Its right edge lands on 122u — exactly where the
   radar begins — so the two tile with no overlap. Both labels wrap to two
   lines inside it; without this cap they run the full width of the card and
   the radar is drawn straight over them. */
.bc__stack{
  inline-size: calc(94 * var(--u));
  white-space: normal;
}

/* RIGHT-anchored. `left:auto` is not optional — it cancels .bc__item's left.
   This is the single rule that positions the radar; do not add another. */
.radar{
  position: absolute;
  left: auto;
  right: calc(10 * var(--u));
  top: calc(104 * var(--u));
  inline-size: calc(275 * var(--u));
  block-size: calc(250 * var(--u));
}
.radar > svg{ inline-size:100%; block-size:100%; overflow:visible; }

.radar__rings path{ fill:none; stroke:#383838; stroke-width:1.202; }
.radar__area2{ fill:rgba(255,255,255,0.2); stroke:rgba(255,255,255,0.1); }
/* White, NOT the #4AFAA9 the source file stores — the artboard resolves that
   library variable monochrome. Stored colour is not painted colour anywhere. */
.radar__area1{ fill:none; stroke:var(--white); stroke-width:2; }
.radar__months text{
  fill:#C6C6C6; font-family:'DM Sans', var(--font-sans); font-weight:500;
  font-size:12px; letter-spacing:-0.02em; text-anchor:middle;
}

.radar__chips{ position:absolute; left:calc(120.5*var(--u)); top:calc(25*var(--u)); }
.radar__chips li{
  position:absolute; top:calc(var(--y) * var(--u));
  display:flex; align-items:center;
  padding:calc(2*var(--u)) calc(4*var(--u));
  border-radius:calc(6*var(--u));
  background:#1C1C1C;                     /* first chip: nearly invisible */
  font-family:'DM Sans', var(--font-sans); font-weight:500;
  font-size:calc(12*var(--u)); line-height:1;
  letter-spacing:var(--track-narrow); color:var(--white); white-space:nowrap;
}
.radar__chips li + li{ background:var(--white); color:#0B0B0B; }
──────────────────────────────────────────────────────────────────────────

╔═════════════════════════════════════════════════════════════════════════╗
║ 9.7 CARD "ASSET CLASS" — ALSO USE VERBATIM                              ║
║                                                                         ║
║ Previously broken: the percentage values floated at the top of the card.║
║ CAUSE: the four values are .bc__item and position from the CARD, while  ║
║ the four bars position from the .allocation box (itself a .bc__item at  ║
║ 15.02,176). Two different origins. Do not nest the values inside        ║
║ .allocation and do not re-base their coordinates.                       ║
╚═════════════════════════════════════════════════════════════════════════╝

── HTML ──────────────────────────────────────────────────────────────────
<article class="board-card board-card--assets">
  <p class="bc__item card__label" style="--x:27; --y:26">Portfolio composition</p>
  <p class="bc__item pill pill--card bc__item--end bc__tag"
     style="--x:27; --y:19">Asset class</p>

  <!-- card-relative, one per bar, sitting ~35u above each bar's top edge -->
  <p class="bc__item allocation__value" style="--x:47;  --y:226">13%</p>
  <p class="bc__item allocation__value" style="--x:132; --y:144">34%</p>
  <p class="bc__item allocation__value" style="--x:229; --y:192">23%</p>
  <p class="bc__item allocation__value" style="--x:316; --y:144">34%</p>

  <div class="bc__item allocation" style="--x:15.02; --y:176">
    <span class="allocation__bar" style="--x:0;      --h:56.82"></span>
    <span class="allocation__bar" style="--x:88.5;   --h:144"></span>
    <span class="allocation__bar" style="--x:177;    --h:94.04"></span>
    <span class="allocation__bar allocation__bar--lead" style="--x:286.98; --h:138.45"></span>
    <span class="allocation__tick" style="--x:3.98">Equities</span>
    <span class="allocation__tick" style="--x:94.98">Bonds</span>
    <span class="allocation__tick" style="--x:217.98">Cash</span>
    <span class="allocation__tick" style="--x:293.98">Alternatives</span>
  </div>
</article>

── CSS ───────────────────────────────────────────────────────────────────
.board-card--assets{ block-size: calc(385 * var(--u)); }
.bc__tag{ inline-size: calc(104 * var(--u)); }
.allocation{
  position:absolute;                       /* via .bc__item */
  inline-size: calc(356.98 * var(--u));
  block-size:  calc(172 * var(--u));
}
.allocation__value{
  font-size: calc(20 * var(--u)); line-height:1;
  letter-spacing: var(--track-narrow); color: var(--white);
}
/* Bars grow UP from a shared baseline 27.55u above the box's bottom. */
.allocation__bar{
  position:absolute;
  left: calc(var(--x) * var(--u));
  bottom: calc(27.55 * var(--u));
  inline-size: calc(68.83 * var(--u));
  block-size: calc(var(--h) * var(--u));
  border-radius: calc(12 * var(--u));
  /* Diagonal hatch on near-black. The ramp is SOFT, not switched — the
     artboard's lines are feathered at this scale. */
  background:
    repeating-linear-gradient(-20.209deg,
      rgba(255,255,255,0) 0,
      rgba(255,255,255,0.32) calc(2 * var(--u)),
      rgba(255,255,255,0) calc(4 * var(--u))),
    #0B0B0B;
}
.allocation__bar--lead{ inline-size: calc(70 * var(--u)); background:#D2D2D2; }
.allocation__tick{
  position:absolute; top: calc(160 * var(--u)); left: calc(var(--x) * var(--u));
  font-size: calc(12 * var(--u)); line-height:1;
  letter-spacing: var(--track-narrow); color: var(--white);
}
──────────────────────────────────────────────────────────────────────────

9.8 COLUMN 3, CARD "digest" (407 × 264u)
  "WEEKLY PORTFOLIO DIGEST" 27,29
  pill--card "Rebalance needed" 29,75
  note--bright "Bond allocation drifted 4.2%" 29,118  (opacity 1, not .4)
  digest__detail 27,196:
   "· Bond allocation drifted 4.2% from target<br>· Recommended shift: -3% bonds, +3% equities"
.digest__detail{inline-size:calc(298*var(--u)); font-size:calc(14*var(--u));
  line-height:1.5; letter-spacing:var(--track-narrow); color:#fff; opacity:.4}

9.9 CLOSING LINE — absolutely positioned, NOT in flow
<p class="signal__title"><span data-reveal="signal-title">Every advisor, every
client, one clear signal</span></p>
.signal__title{position:absolute; top:calc(1008*var(--u)); left:0; right:0;
  margin:0; font-size:calc(48*var(--u)); line-height:1;
  letter-spacing:var(--track-tight); text-align:center}
.signal__title > span{display:inline-block;
  background-image:linear-gradient(180deg,#fff,#999); background-clip:text;
  color:transparent}
  ← the wash lives on the SPAN: the wipe masks it, and a masked child cannot
    show through an ancestor's background-clip:text.

╔═════════════════════════════════════════════════════════════════════════╗
║ 10. ANIMATION + TIMING  (this is already correct — do not change it)    ║
╚═════════════════════════════════════════════════════════════════════════╝
10.1 CLASS SEQUENCE (t = 0 is the gesture)
  t=0     add .to-signal
  t=1000  add .signal-shown   (= --morph-dur, read back via getComputedStyle;
          parse "1000ms" vs "1s". At this instant the two sections are
          pixel-identical, which is what makes the swap invisible.)
  t=1020  add .signal-in      (a 20ms TIMER, not rAF: frames are not guaranteed
          when nothing is moving, and the section must be on screen before the
          opacity transitions start or they will not animate.)
  Reverse: remove .signal-in, .signal-shown, .to-signal; clear card transforms.

10.2 DURING THE TRAVEL (0 → 1000ms), all on cubic-bezier(0.33,0,0.2,1)
  a) .morph-ready .panel .card{transition:transform var(--morph-dur) var(--morph-ease);
       will-change:transform}                                 0 → 1000
     (transform written inline by JS — measured, never authored)
  b) .morph-ready .hero__glow{transition:left var(--morph-dur) var(--morph-ease),
       border-radius var(--morph-dur) var(--morph-ease)}      0 → 1000
     .to-signal .hero__glow{left:0; border-radius:0}
  c) .morph-ready .hero__body,.trusted{transition:opacity 330ms var(--morph-ease),
       transform 500ms var(--morph-ease)}
     .to-signal …{opacity:0; transform:translateX(var(--hero-out-x,-12vw))}
  d) .morph-ready .panel__title,.panel__caption{
       transition:opacity 500ms var(--morph-ease) 350ms}      350 → 850
     .to-signal …{opacity:0}
  e) .morph-ready .section--hero .brand,.mainnav,.btn-nav{
       transition:transform 850ms var(--morph-ease) 150ms}    150 → 1000
     .to-signal … translateX(var(--brand-dx / --nav-dx / --cta-dx))
  f) .morph-ready:not(.signal-shown) [data-board-col1]{visibility:hidden}
     .morph-ready [data-board-col1]{position:relative; z-index:1}

10.3 AFTER THE SWAP (relative to .signal-in)
  g) .board{background:none}
     .board::before{content:''; position:absolute; inset:0; border-radius:inherit;
       background:linear-gradient(180deg,var(--surface-top) 0%,rgba(25,25,25,.4) 100%);
       transform-origin:50% 0}
     .morph-ready .board::before{clip-path:inset(0 0 100% 0)}
     .signal-in .board::before{clip-path:inset(0 0 0% 0);
       transition:clip-path 370ms var(--morph-ease) 170ms}   +170 → +540
     ← a separate layer so growing it cannot squash the column on top of it.
  h) .morph-ready [data-reveal='board-mid'],[data-reveal='board-end']{
       animation:none; opacity:0}
     .signal-in …{opacity:1; transition:opacity 200ms var(--morph-ease) 390ms}
                                                              +390 → +590
  i) .morph-ready [data-reveal='signal-masthead']{animation:none; opacity:1}
  j) @property --wipe{syntax:'<percentage>'; inherits:false; initial-value:0%}
     .morph-ready [data-reveal='signal-title']{animation:none; opacity:1; --wipe:0%;
       mask-image:linear-gradient(90deg,#000 0 calc(var(--wipe) - 12.5%),
         transparent var(--wipe))}
     .signal-in [data-reveal='signal-title']{
       animation:signal-wipe 566ms linear 900ms forwards}     +900 → +1466
     @keyframes signal-wipe{from{--wipe:63.25%} to{--wipe:112.5%}}
     ← `forwards` NOT `both`: through the 900ms delay the element keeps its own
       --wipe:0%; `backwards` would show 57% of the line early.

10.4 TIMELINE (ms from gesture)
   0 ──────────────────────────────── 1000  cards travel / glow widens
   0 ─── 330                                hero words fade
   0 ────── 500                             hero words slide
        350 ────── 850                      panel captions fade
     150 ─────────────────────────── 1000   masthead slides
                                     1000   ▲ SWAP      1020 .signal-in
                              1190 ── 1560  board surface wipes down
                                 1410 ─ 1610 columns 2 & 3 fade
                                      1920 ─── 2486  closing line wipes

═══════════════════════════════════════════════════════════════════════════
11. JAVASCRIPT (vanilla IIFE)
═══════════════════════════════════════════════════════════════════════════
1. DURATION — read --morph-dur from getComputedStyle; parse ms/s; default 1000.
2. buildChartGrid() — per [data-chart-grid], append 14 <line>: x = i*21 + 0.25,
   y1=0 y2=120, stroke #FFFFFF, stroke-width 0.5, stroke-dasharray "1 5",
   stroke-opacity 0.3, in the SVG's own coordinate space.
3. measureMarquee() — shift = logos[half].left − logos[0].left; write
   --marquee-shift as negative px (≈644.1u). Re-run on viewport change.
4. Transition() — measure both ends with getBoundingClientRect, write the card
   transform and --brand-dx/--nav-dx/--cta-dx inline, drive §10.1.
   Input (NOT scroll): wheel (preventDefault; 260ms quiet lock clears so a
   trackpad's inertial tail cannot bounce; ignore |deltaY|<4; one gesture = one
   section) · touch (fires once |dy| ≥ 24, then nulls the anchor) · keydown
   (ArrowDown/PageDown/Space/End forward; ArrowUp/PageUp/Home back).
   Read --morph back from the stylesheet rather than measuring the window.
   Re-measure on resize via a rAF-debounced handler.
5. releaseReveals() — animationend (capture) → add .is-revealed, clear will-change.
6. Menu() — burger toggle, aria-expanded, Escape closes, outside click closes.
Add .is-ready on load; .morph-ready once measured.

═══════════════════════════════════════════════════════════════════════════
12. RESPONSIVE
═══════════════════════════════════════════════════════════════════════════
Desktop needs NO breakpoint — a pure scale holding 2560 → ~1024.

TABLET @media (max-width:1023px) and (max-aspect-ratio:5/4)
  --u: min(calc(100vw/917), calc(100vh/1470), var(--u-max));
  .hero__grid{grid-template-columns:100%; align-content:space-between}
    ← space-between, NOT centre (centring puts the burger under its own menu).
  .hero__content{padding-block:calc(40*var(--u)) 0}
  .hero__body{gap:calc(34*var(--u))} .hero__head{margin-block-end:0}
  .trusted{margin-block-start:calc(34*var(--u))}
  .hero__panel{padding-block:calc(30*var(--u)) calc(40*var(--u)); justify-content:flex-start}
  .panel{display:flex; flex-wrap:wrap; justify-content:center; align-items:flex-start;
    gap:calc(11*var(--u)); inline-size:min(calc(667*var(--u)),100%)}
  .panel .card{inline-size:calc(328*var(--u)); flex:none}
  .card--value{margin-block-start:0}
  .panel__title,.panel__caption{flex-basis:100%}
  .panel__title{margin-block-start:calc(34*var(--u))}
  .hero__glow{inset-inline:0; inset-block:auto 0; block-size:calc(578*var(--u));
    border-radius:var(--radius-glow) var(--radius-glow) 0 0}
  .masthead{grid-template-columns:1fr auto}
  .masthead .mainnav,.btn-nav{display:none}  .masthead .burger{display:flex}
  SIGNAL → three rows of two, in flow:
  .section--signal{align-items:flex-start}
  .signal{block-size:auto; display:flex; flex-direction:column; align-items:center}
  .signal__masthead{position:static; align-self:stretch;
    margin-block-start:calc(40*var(--u)); padding-inline:var(--gutter)}
  .board{position:relative; inset:auto; block-size:auto;
    inline-size:min(calc(854*var(--u)),100%); margin-block-start:calc(24*var(--u));
    flex-direction:column; gap:calc(12*var(--u)); padding:calc(14*var(--u))}
    ← `relative` REQUIRED: a static board hands its ::before to the whole signal
      shell. `inset:auto` — top:236u is inert while static, not once positioned.
  .board__col{flex:none; inline-size:100%; flex-direction:row; align-items:flex-start;
    justify-content:center; gap:calc(12*var(--u)); margin-inline-start:0}
  .board__col--allocation{gap:calc(11*var(--u))}
  .board__col--allocation .board-card{inline-size:calc(328*var(--u))}
  .board__col--mid .board-card,.board__col--end .board-card{flex:none;
    inline-size:calc(407*var(--u))}
    ← cards NEVER narrow: their contents sit at fixed --x coordinates, so a
      narrower card draws its own text past its edge. The radar's 122u tiling
      breaks the moment the card is not 407u.
  .signal__title{position:static; align-self:stretch;
    margin-block-start:calc(24*var(--u)); padding-inline:var(--gutter)}
  Travel adapted — the glow grows UPWARD (it entered from the bottom):
  .morph-ready .hero__glow{transition:block-size var(--morph-dur) var(--morph-ease),
    border-radius var(--morph-dur) var(--morph-ease)}
  .to-signal .hero__glow{left:0; block-size:100%; border-radius:0}

MOBILE @media (max-width:599px), (max-height:479px) and (max-width:1023px)
  --u: min(calc(100vw/866), calc(100vh/1450), var(--u-max));
  --gutter:calc(14*var(--u));  --rv-scale:0.6;
  .hero__grid{grid-template-rows:1fr auto; align-content:stretch}
  .hero__content{padding-block:calc(30*var(--u)) calc(40*var(--u))}
  .hero__body{gap:calc(26*var(--u))} .trusted{margin-block-start:calc(26*var(--u))}
  .hero__panel{padding-block:calc(24*var(--u)) calc(28*var(--u))}
  .hero__glow{block-size:calc(556*var(--u))}
  .panel__title{margin-block-start:calc(26*var(--u))}
  .hero__title{font-size:calc(72*var(--u))}  .signal__title{font-size:calc(40*var(--u))}
  .board{padding:calc(8*var(--u)); gap:calc(8*var(--u));
    margin-block-start:calc(18*var(--u)); inline-size:min(calc(838*var(--u)),100%)}
  .board__col,.board__col--allocation{gap:calc(8*var(--u))}
  .signal__masthead{margin-block-start:calc(30*var(--u))}
  .signal__title{margin-block-start:calc(18*var(--u))}
  .menu{inset-block-start:calc(96*var(--u))}

MENU (tablet/mobile): fixed panel below the masthead; surface is the card's own —
rgba(0,0,0,.325) over a backdrop blur, 10% gradient hairline, 25u radius.

REDUCED MOTION @media (prefers-reduced-motion:reduce)
  .marquee__track{animation:none}
  *,*::before,*::after{transition-duration:.01ms !important}
  .js [data-reveal]{opacity:1}  .is-ready [data-reveal]{animation:none}
  .morph-ready [data-reveal='board-mid'],[data-reveal='board-end'],
    [data-reveal='signal-masthead']{opacity:1}
  .morph-ready [data-reveal='signal-title']{--wipe:112.5%}
  .morph-ready .hero__glow{transition:none}
  .morph-ready .panel{transform:none !important}

═══════════════════════════════════════════════════════════════════════════
13. ACCEPTANCE CHECKS — run these in the console before declaring done
═══════════════════════════════════════════════════════════════════════════
□ ADVISOR CARD — paste and confirm all three read 122 / 122 / 0:
    const c=document.querySelector('.board-card--activity').getBoundingClientRect();
    const r=document.querySelector('.board-card--activity .radar').getBoundingClientRect();
    const s=document.querySelector('.board-card--activity .bc__stack').getBoundingClientRect();
    const u=c.width/407;
    console.log({stackRight:((s.right-c.left)/u).toFixed(1),
                 radarLeft:((r.left-c.left)/u).toFixed(1),
                 overlap:((s.right-r.left)/u).toFixed(1),
                 radarRightGap:((c.right-r.right)/u).toFixed(1)});
    → stackRight 122.0 · radarLeft 122.0 · overlap 0.0 · radarRightGap 10.0
    If radarLeft is ~10, the radar is anchored with `left` — fix rule 1.
□ "Portfolios reviewed" and "Rebalances triggered" wrap to two lines; "Risk
  profile" does not.
□ ASSET CARD — the four % values sit directly above their own bars, inside the
  lower half of the card. None near the title.
□ Board is exactly 1198×689u, centred, top 236u. Every column measures 661u.
□ Page never scrolls; no scrollbar in either axis.
□ Video 1080x1916, ~10.04s, looping with no visible snap.
□ Marquee cycle ≈644.1u; glyph gaps = 49u.
□ Title gradient reads as ONE wash across all three lines.
□ Closing line arrives by a left-to-right soft-edged wipe, never a fade.
□ At 390x844 the whole composition fits one screen with no overflow.
□ Nothing in fixed px except --u-max and --shell-max.
[/hidecontent]
← 返回首页