dsh-drawai 0.4.2 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -5
- package/lib/client.js +142 -16
- package/lib/index.js +7945 -7719
- package/lib/motion/asset-pack.js +394 -394
- package/lib/motion/assets.js +291 -291
- package/lib/motion/demo-assets-lock.js +10 -10
- package/lib/motion/mgstyles/briefs/23-ink-wash.md +147 -0
- package/lib/motion/mgstyles/catalog.d/23-ink-wash.json +28 -0
- package/lib/motion/mgstyles/catalog.json +127 -14
- package/lib/motion/mgstyles/upstream/demos/23-ink-wash/brush.js +149 -0
- package/lib/motion/mgstyles/upstream/demos/23-ink-wash/film.js +202 -0
- package/lib/motion/mgstyles/upstream/manifest.json +28 -3
- package/lib/motion/mgstyles/upstream/prompts/23-ink-wash.md +59 -0
- package/lib/motion/mgstyles/vendor/README.md +28 -28
- package/lib/motion/mgstyles/vendor/modules/d3/LICENSE +13 -13
- package/lib/motion/mgstyles/vendor/modules/d3/dist/d3.min.js +2 -2
- package/lib/motion/mgstyles/vendor/modules/opentype.js/LICENSE +20 -20
- package/lib/motion/mgstyles/vendor/modules/opentype.js/dist/opentype.mjs +14460 -14460
- package/lib/motion/mgstyles/vendor/modules/simplex-noise/LICENSE +21 -21
- package/lib/motion/mgstyles/vendor/modules/simplex-noise/dist/esm/simplex-noise.js +465 -465
- package/lib/motion/mgstyles/vendor/modules/topojson-client/LICENSE +13 -13
- package/lib/motion/mgstyles/vendor/modules/topojson-client/dist/topojson-client.min.js +2 -2
- package/lib/motion/project.js +5 -1
- package/lib/motion/qa.js +116 -6
- package/lib/motion/render.js +179 -179
- package/lib/motion/scene-kit.js +1 -1
- package/lib/motion/shotcraft/upstream/SKILL.md +286 -286
- package/lib/motion/shotcraft/upstream/assets/lib/Caption.tsx +54 -54
- package/lib/motion/shotcraft/upstream/assets/lib/ClipCard.tsx +200 -200
- package/lib/motion/shotcraft/upstream/assets/lib/DigitRoll.tsx +45 -45
- package/lib/motion/shotcraft/upstream/assets/lib/FlashCut.tsx +20 -20
- package/lib/motion/shotcraft/upstream/assets/lib/FlatPanel.tsx +65 -65
- package/lib/motion/shotcraft/upstream/assets/lib/PageCam.tsx +151 -151
- package/lib/motion/shotcraft/upstream/assets/lib/VerticalTicker.tsx +129 -129
- package/lib/motion/shotcraft/upstream/assets/lib/helpers/camera.tsx +75 -75
- package/lib/motion/shotcraft/upstream/assets/lib/helpers/motion.ts +40 -40
- package/lib/motion/shotcraft/upstream/assets/lib/helpers/rand.ts +11 -11
- package/lib/motion/shotcraft/upstream/assets/lib/helpers/shake.ts +10 -10
- package/lib/motion/shotcraft/upstream/assets/scripts/build-readme-cover.mjs +49 -49
- package/lib/motion/shotcraft/upstream/assets/scripts/capture-template.mjs +210 -210
- package/lib/motion/shotcraft/upstream/assets/scripts/sfx-source.py +423 -423
- package/lib/motion/shotcraft/upstream/assets/scripts/smoke-render-demos.py +170 -170
- package/lib/motion/shotcraft/upstream/assets/scripts/test_sfx_source.py +157 -157
- package/lib/motion/shotcraft/upstream/demos/README.md +120 -120
- package/lib/motion/shotcraft/upstream/demos/_fixtures/Brand.tsx +112 -112
- package/lib/motion/shotcraft/upstream/demos/_fixtures/Fixtures.tsx +647 -647
- package/lib/motion/shotcraft/upstream/demos/_fixtures/Look.tsx +316 -316
- package/lib/motion/shotcraft/upstream/demos/_fixtures/Motion.tsx +82 -82
- package/lib/motion/shotcraft/upstream/demos/_fixtures/PageCam2D.tsx +115 -115
- package/lib/motion/shotcraft/upstream/demos/_fixtures/Polish.tsx +243 -243
- package/lib/motion/shotcraft/upstream/demos/_textures/live-layout.json +388 -388
- package/lib/motion/shotcraft/upstream/demos/camera/basic-3d-scene/Basic3DScene.tsx +250 -250
- package/lib/motion/shotcraft/upstream/demos/camera/crash-zoom-punch/CrashImpactReal.tsx +305 -305
- package/lib/motion/shotcraft/upstream/demos/camera/crash-zoom-punch/CrashZoomReal.tsx +246 -246
- package/lib/motion/shotcraft/upstream/demos/camera/cursor-flyover/CursorFlyover.tsx +390 -390
- package/lib/motion/shotcraft/upstream/demos/camera/depth-layer-moves/MultiplaneReal.tsx +138 -138
- package/lib/motion/shotcraft/upstream/demos/camera/graze-face-tour/GrazeFaceTour.tsx +542 -542
- package/lib/motion/shotcraft/upstream/demos/camera/overhead-camera-moves/OverheadTabletopDrop.tsx +315 -315
- package/lib/motion/shotcraft/upstream/demos/camera/overhead-camera-moves/TiltReveal.tsx +299 -299
- package/lib/motion/shotcraft/upstream/demos/camera/space-camera-moves/DroneDiveLanding.tsx +173 -173
- package/lib/motion/shotcraft/upstream/demos/camera/space-camera-moves/ExplodedView.tsx +348 -348
- package/lib/motion/shotcraft/upstream/demos/camera/steep-tilt-glide/SteepTiltGlide.tsx +366 -366
- package/lib/motion/shotcraft/upstream/demos/camera/tension-camera-moves/BulletTimeFreezeOrbit.tsx +279 -279
- package/lib/motion/shotcraft/upstream/demos/camera/tension-camera-moves/PullBackIsolation.tsx +184 -184
- package/lib/motion/shotcraft/upstream/demos/camera/terminal-3d/Terminal3D.tsx +390 -390
- package/lib/motion/shotcraft/upstream/demos/data/before-after-slider-scrub/BeforeAfterSliderScrub.tsx +155 -155
- package/lib/motion/shotcraft/upstream/demos/data/chart-live-moves/AxisRescaleShockV2.tsx +303 -303
- package/lib/motion/shotcraft/upstream/demos/data/chart-live-moves/OscilloscopeStreamV2.tsx +290 -290
- package/lib/motion/shotcraft/upstream/demos/data/chart-live-moves/UnitDotSwarmRegroupV2.tsx +278 -278
- package/lib/motion/shotcraft/upstream/demos/data/gauge-readout-moves/NeedleSweepSelftest.tsx +307 -307
- package/lib/motion/shotcraft/upstream/demos/data/gauge-readout-moves/TapeScrollFixedPointer.tsx +215 -215
- package/lib/motion/shotcraft/upstream/demos/data/scroll-brake-moves/BrakeReticleLock.tsx +299 -299
- package/lib/motion/shotcraft/upstream/demos/data/scroll-brake-moves/ChangelogScrollBrake.tsx +232 -232
- package/lib/motion/shotcraft/upstream/demos/data/timeline-travel/TimelineTravel.tsx +290 -290
- package/lib/motion/shotcraft/upstream/demos/effects/assemble-then-type-flyin/AssembleThenTypeFlyin.tsx +273 -273
- package/lib/motion/shotcraft/upstream/demos/effects/aurora-bloom-bg-flip/AuroraBloomBgFlip.tsx +167 -167
- package/lib/motion/shotcraft/upstream/demos/effects/brand-frame-snap/BrandFrameSnap.tsx +536 -536
- package/lib/motion/shotcraft/upstream/demos/effects/fui-hud-moves/ReticleLockOn.tsx +164 -164
- package/lib/motion/shotcraft/upstream/demos/effects/glow-flyline-moves/FlylineArc.tsx +175 -175
- package/lib/motion/shotcraft/upstream/demos/effects/glow-flyline-moves/GlowOrbAmbient.tsx +197 -197
- package/lib/motion/shotcraft/upstream/demos/effects/glow-flyline-moves/OrbFlylineRelay.tsx +241 -241
- package/lib/motion/shotcraft/upstream/demos/effects/icon-performance-moves/AttentionBounce.tsx +279 -279
- package/lib/motion/shotcraft/upstream/demos/effects/impact-feedback/AnimeImpact.tsx +230 -230
- package/lib/motion/shotcraft/upstream/demos/effects/impact-feedback/HitCounter.tsx +345 -345
- package/lib/motion/shotcraft/upstream/demos/effects/light-play-moves/SheenSweepRetry.tsx +275 -275
- package/lib/motion/shotcraft/upstream/demos/effects/light-play-moves/SpotlightSweepReveal.tsx +229 -229
- package/lib/motion/shotcraft/upstream/demos/effects/radial-ripple-phone-chips/RadialRipplePhoneChips.tsx +312 -312
- package/lib/motion/shotcraft/upstream/demos/effects/riso-print-hits/RisoMisregistrationHit.tsx +222 -222
- package/lib/motion/shotcraft/upstream/demos/effects/scan-bracket-sweep/ScanBracketSweep.tsx +295 -295
- package/lib/motion/shotcraft/upstream/demos/effects/scanline-annotate-focus/ScanlineAnnotateFocus.tsx +329 -329
- package/lib/motion/shotcraft/upstream/demos/effects/scanline-assemble-flyin/ScanlineAssembleFlyin.tsx +315 -315
- package/lib/motion/shotcraft/upstream/demos/effects/slam-entrance-moves/ImpactBurstKit.tsx +264 -264
- package/lib/motion/shotcraft/upstream/demos/effects/slam-entrance-moves/KanadaPerspectiveSnap.tsx +260 -260
- package/lib/motion/shotcraft/upstream/demos/effects/slam-entrance-moves/ScoreSlam.tsx +263 -263
- package/lib/motion/shotcraft/upstream/demos/effects/spotlight-sweep-moves/CornerSpotlightReveal.tsx +123 -123
- package/lib/motion/shotcraft/upstream/demos/effects/spotlight-sweep-moves/GlowWakeSleepPanel.tsx +187 -187
- package/lib/motion/shotcraft/upstream/demos/effects/spotlight-sweep-moves/SlideSpotlightPan.tsx +175 -175
- package/lib/motion/shotcraft/upstream/demos/interaction/autolayout-gap-dial/AutolayoutGapDial.tsx +305 -305
- package/lib/motion/shotcraft/upstream/demos/interaction/canvas-materialize-moves/DiagramCascadeBuild.tsx +350 -350
- package/lib/motion/shotcraft/upstream/demos/interaction/canvas-materialize-moves/PanelToCanvasMaterialize.tsx +352 -352
- package/lib/motion/shotcraft/upstream/demos/interaction/chip-grid-single-select-blackout/ChipGridSingleSelectBlackout.tsx +218 -218
- package/lib/motion/shotcraft/upstream/demos/interaction/collab-cursor-moves/CursorCastEnsemble.tsx +297 -297
- package/lib/motion/shotcraft/upstream/demos/interaction/glass-pill-dictation-typing/GlassPillDictationTyping.tsx +157 -157
- package/lib/motion/shotcraft/upstream/demos/interaction/hashtag-to-pill-materialize/HashtagToPillMaterialize.tsx +218 -218
- package/lib/motion/shotcraft/upstream/demos/interaction/picker-carousel-feature-cycle/PickerCarouselFeatureCycle.tsx +202 -202
- package/lib/motion/shotcraft/upstream/demos/interaction/segmented-thumb-hero/SegmentedThumbHero.tsx +265 -265
- package/lib/motion/shotcraft/upstream/demos/interaction/theme-switch-moves/PaletteThemeRipple.tsx +457 -457
- package/lib/motion/shotcraft/upstream/demos/interaction/theme-switch-moves/ThemeSweepToggle.tsx +446 -446
- package/lib/motion/shotcraft/upstream/demos/interaction/type-and-filter/TypeAndFilter.tsx +249 -249
- package/lib/motion/shotcraft/upstream/demos/interaction/voice-waveform-live/VoiceWaveformLive.tsx +278 -278
- package/lib/motion/shotcraft/upstream/demos/opening/crane-rise-reveal/CraneRiseReveal.tsx +332 -332
- package/lib/motion/shotcraft/upstream/demos/opening/dataviz-landscape-open/DatavizLandscapeOpen.tsx +358 -358
- package/lib/motion/shotcraft/upstream/demos/opening/fracture/Fracture.tsx +201 -201
- package/lib/motion/shotcraft/upstream/demos/opening/icon-field-colorize/IconFieldColorize.tsx +200 -200
- package/lib/motion/shotcraft/upstream/demos/opening/letterspace-materialize/LetterspaceMaterialize.tsx +208 -208
- package/lib/motion/shotcraft/upstream/demos/opening/magician-card-flourish/MagicianCardFlourish.tsx +341 -341
- package/lib/motion/shotcraft/upstream/demos/opening/orbit-ring-title-open/OrbitRingTitleOpen.tsx +389 -389
- package/lib/motion/shotcraft/upstream/demos/opening/spotlight-hero-card/SpotlightHeroCard.tsx +293 -293
- package/lib/motion/shotcraft/upstream/demos/opening/stroke-segment-build/StrokeSegmentBuild.tsx +183 -183
- package/lib/motion/shotcraft/upstream/demos/opening/text-as-mask/TextAsMask.tsx +245 -245
- package/lib/motion/shotcraft/upstream/demos/outro/grain-dissolve/GrainDissolve.tsx +222 -222
- package/lib/motion/shotcraft/upstream/demos/outro/logo-shrink-wordmark-lockup/LogoShrinkWordmarkLockup.tsx +223 -223
- package/lib/motion/shotcraft/upstream/demos/outro/neon-triple-marquee/NeonTripleMarquee.tsx +217 -217
- package/lib/motion/shotcraft/upstream/demos/outro/outro-group-photo-launch/OutroGroupPhotoLaunch.tsx +343 -343
- package/lib/motion/shotcraft/upstream/demos/outro/ui-strip-away-outro/UiStripAwayOutro.tsx +357 -357
- package/lib/motion/shotcraft/upstream/demos/outro/ui-to-brand-morph/IconFlipBloomLogo.tsx +187 -187
- package/lib/motion/shotcraft/upstream/demos/outro/ui-to-brand-morph/InputMorphsIntoLogo.tsx +333 -333
- package/lib/motion/shotcraft/upstream/demos/rhythm/beat-cut-moves/PaparazziFlash.tsx +274 -274
- package/lib/motion/shotcraft/upstream/demos/rhythm/beat-step-list-theme-cycle/BeatStepListThemeCycle.tsx +270 -270
- package/lib/motion/shotcraft/upstream/demos/rhythm/panel-grid-moves/ComicPanelSplit.tsx +430 -430
- package/lib/motion/shotcraft/upstream/demos/rhythm/quad-split-parallel-scenes/QuadSplitParallelScenes.tsx +252 -252
- package/lib/motion/shotcraft/upstream/demos/rhythm/rhythm-interrupt-moves/JumpCutPunchIn.tsx +106 -106
- package/lib/motion/shotcraft/upstream/demos/rhythm/sakuga-timing-shift/SakugaTimingShift.tsx +288 -288
- package/lib/motion/shotcraft/upstream/demos/rhythm/speed-ramp-freeze/FreezeAnnotateReal.tsx +362 -362
- package/lib/motion/shotcraft/upstream/demos/rhythm/trailer-grammar-moves/TrailerBumper.tsx +168 -168
- package/lib/motion/shotcraft/upstream/demos/transition/bottom-push-stack-wipe/BottomPushStackWipe.tsx +310 -310
- package/lib/motion/shotcraft/upstream/demos/transition/card-flip-reveal/CardFlipReveal.tsx +261 -261
- package/lib/motion/shotcraft/upstream/demos/transition/card-flock-tumble/CardFlockTumble.tsx +459 -459
- package/lib/motion/shotcraft/upstream/demos/transition/circle-match-iris/CircleMatchIris.tsx +294 -294
- package/lib/motion/shotcraft/upstream/demos/transition/gradient-transition/GradientTransition.tsx +161 -161
- package/lib/motion/shotcraft/upstream/demos/transition/line-carry-transition/LineCarryTransition.tsx +248 -248
- package/lib/motion/shotcraft/upstream/demos/transition/mosaic-reframe/MosaicReframe.tsx +359 -359
- package/lib/motion/shotcraft/upstream/demos/transition/page-turn-transitions/BarnDoorSplit.tsx +227 -227
- package/lib/motion/shotcraft/upstream/demos/transition/print-texture-transitions/InkBleedReveal.tsx +162 -162
- package/lib/motion/shotcraft/upstream/demos/transition/shot-transitions/PortalWipeV2.tsx +248 -248
- package/lib/motion/shotcraft/upstream/demos/transition/shot-transitions/WhipBrakeReal.tsx +186 -186
- package/lib/motion/shotcraft/upstream/demos/transition/tear-streak-transitions/GlitchDisplace.tsx +355 -355
- package/lib/motion/shotcraft/upstream/demos/transition/transition-hidden-cut/InvisibleCut.tsx +272 -272
- package/lib/motion/shotcraft/upstream/demos/transition/transition-hidden-cut/LightLeakBurn.tsx +262 -262
- package/lib/motion/shotcraft/upstream/demos/transition/transition-hidden-cut/VersusSlam.tsx +217 -217
- package/lib/motion/shotcraft/upstream/demos/transition/transition-travel/LetterformZoom.tsx +285 -285
- package/lib/motion/shotcraft/upstream/demos/transition/white-flash-logo-simplify-cut/WhiteFlashLogoSimplifyCut.tsx +205 -205
- package/lib/motion/shotcraft/upstream/demos/transition/wipe-transitions/BlindsSlice.tsx +178 -178
- package/lib/motion/shotcraft/upstream/demos/typography/blur-slide/BlurSlide.tsx +147 -147
- package/lib/motion/shotcraft/upstream/demos/typography/brace-expand/BraceExpand.tsx +143 -143
- package/lib/motion/shotcraft/upstream/demos/typography/brand-ink-open/BrandInkOpen.tsx +134 -134
- package/lib/motion/shotcraft/upstream/demos/typography/countdown-arc-scatter/CountdownArcScatter.tsx +217 -217
- package/lib/motion/shotcraft/upstream/demos/typography/flying-words/FlyingWords.tsx +198 -198
- package/lib/motion/shotcraft/upstream/demos/typography/glitch-cycle/GlitchCycle.tsx +221 -221
- package/lib/motion/shotcraft/upstream/demos/typography/gradient-word-sweep/GradientWordSweep.tsx +312 -312
- package/lib/motion/shotcraft/upstream/demos/typography/lead-word-zoom-assemble/LeadWordZoomAssemble.tsx +261 -261
- package/lib/motion/shotcraft/upstream/demos/typography/marker-underline-title/MarkerUnderlineTitle.tsx +121 -121
- package/lib/motion/shotcraft/upstream/demos/typography/outline-word-fill/OutlineWordFill.tsx +153 -153
- package/lib/motion/shotcraft/upstream/demos/typography/paper-title-card/PaperTitleCard.tsx +179 -179
- package/lib/motion/shotcraft/upstream/demos/typography/pill-chip-slot-cycle-handled/PillChipSlotCycleHandled.tsx +140 -140
- package/lib/motion/shotcraft/upstream/demos/typography/pill-slot-cycle/PillSlotCycle.tsx +309 -309
- package/lib/motion/shotcraft/upstream/demos/typography/text-column-converge/TextColumnConverge.tsx +191 -191
- package/lib/motion/shotcraft/upstream/demos/typography/title-demote-to-label/TitleDemoteToLabel.tsx +292 -292
- package/lib/motion/shotcraft/upstream/demos/typography/type-assembly-moves/LetterformDriftAssembly.tsx +135 -135
- package/lib/motion/shotcraft/upstream/demos/typography/type-assembly-moves/SplitTextStagger.tsx +124 -124
- package/lib/motion/shotcraft/upstream/demos/typography/type-assembly-moves/TextOnPath.tsx +255 -255
- package/lib/motion/shotcraft/upstream/demos/typography/type-entrance-moves/LetterDropPhysics.tsx +173 -173
- package/lib/motion/shotcraft/upstream/demos/typography/type-rhythm-sync/FontWeightPump.tsx +225 -225
- package/lib/motion/shotcraft/upstream/demos/typography/type-rhythm-sync/KaraokeFillSync.tsx +141 -141
- package/lib/motion/shotcraft/upstream/demos/typography/typewriter-moves/TypewriterErrorRetype.tsx +137 -137
- package/lib/motion/shotcraft/upstream/demos/typography/vertical-word-roll-blur-cycle/VerticalWordRollBlurCycle.tsx +147 -147
- package/lib/motion/shotcraft/upstream/demos/typography/word-relay-filmstrip/WordRelayFilmstrip.tsx +409 -409
- package/lib/motion/shotcraft/upstream/demos/typography/word-relay-geometry/WordRelayGeometry.tsx +280 -280
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/avatar-bracket-carousel/AvatarBracketCarousel.tsx +271 -271
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/bezier-source-converge-merge/BezierSourceConvergeMerge.tsx +385 -385
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/card-stack/CardStack.tsx +312 -312
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/carousel-3d/Carousel3D.tsx +303 -303
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/clipcard-looping/ClipCardLooping.tsx +77 -77
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/cloner-depth-echo/ClonerDepthEcho.tsx +311 -311
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/deck-deal-flyin/DeckDealFlyin.tsx +307 -307
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/doc-park-left-pill-deal/DocParkLeftPillDeal.tsx +349 -349
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/document-typewriter-reveal/DocumentTypewriterReveal.tsx +255 -255
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/draw-svg-trace/DrawSvgTrace.tsx +244 -244
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/element-body-moves/ContactShadowLift.tsx +217 -217
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/integration-hub-map/IntegrationHubMap.tsx +652 -652
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/list-reveal/ListReveal.tsx +153 -153
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/list-stack-press/ListStackPress.tsx +238 -238
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/neon-frame-forerun/NeonFrameForerun.tsx +324 -324
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/neon-frame-orbit-drop/NeonFrameForerunOrbit.tsx +354 -354
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/page-waterfall-wall/PageWaterfallWall.tsx +101 -101
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/page-waterfall-wall/VerticalTicker.tsx +129 -129
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/product-card-progressive-assemble/ProductCardProgressiveAssemble.tsx +297 -297
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/radial-wave/RadialWave.tsx +57 -57
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/research-card-stack-scroll/ResearchCardStackScroll.tsx +155 -155
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/row-embed/RowEmbed.tsx +175 -175
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/value-stagger-gradient/ValueStaggerGradient.tsx +153 -153
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/wall-reveal-moves/BentoLightUp.tsx +146 -146
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/wall-reveal-moves/WireframeDrawOn.tsx +226 -226
- package/lib/motion/shotcraft/upstream/gallery/api/library.json +1 -1
- package/lib/motion/shotcraft/upstream/references/aesthetic-rules.md +193 -193
- package/lib/motion/shotcraft/upstream/references/final-review.md +121 -121
- package/lib/motion/shotcraft/upstream/references/guided-free-creation.md +199 -199
- package/lib/motion/shotcraft/upstream/references/jianying-export.md +183 -183
- package/lib/motion/shotcraft/upstream/references/music-beat-sync.md +257 -257
- package/lib/motion/shotcraft/upstream/references/pipeline.md +382 -382
- package/lib/motion/shotcraft/upstream/references/publish-upload-post.md +111 -111
- package/lib/motion/shotcraft/upstream/references/sequences/promo-energy-arc.md +50 -50
- package/lib/motion/shotcraft/upstream/references/sfx-sourcing.md +95 -95
- package/lib/motion/shotcraft/upstream/references/shots/camera/basic-3d-scene.md +41 -41
- package/lib/motion/shotcraft/upstream/references/shots/camera/crash-zoom-punch.md +52 -52
- package/lib/motion/shotcraft/upstream/references/shots/camera/cursor-flyover.md +39 -39
- package/lib/motion/shotcraft/upstream/references/shots/camera/depth-layer-moves.md +39 -39
- package/lib/motion/shotcraft/upstream/references/shots/camera/graze-face-tour.md +57 -57
- package/lib/motion/shotcraft/upstream/references/shots/camera/overhead-camera-moves.md +51 -51
- package/lib/motion/shotcraft/upstream/references/shots/camera/space-camera-moves.md +46 -46
- package/lib/motion/shotcraft/upstream/references/shots/camera/steep-tilt-glide.md +52 -52
- package/lib/motion/shotcraft/upstream/references/shots/camera/tension-camera-moves.md +43 -43
- package/lib/motion/shotcraft/upstream/references/shots/camera/terminal-3d.md +42 -42
- package/lib/motion/shotcraft/upstream/references/shots/data/before-after-slider-scrub.md +48 -48
- package/lib/motion/shotcraft/upstream/references/shots/data/chart-live-moves.md +52 -52
- package/lib/motion/shotcraft/upstream/references/shots/data/gauge-readout-moves.md +45 -45
- package/lib/motion/shotcraft/upstream/references/shots/data/scroll-brake-moves.md +46 -46
- package/lib/motion/shotcraft/upstream/references/shots/data/timeline-travel.md +44 -44
- package/lib/motion/shotcraft/upstream/references/shots/effects/assemble-then-type-flyin.md +63 -63
- package/lib/motion/shotcraft/upstream/references/shots/effects/aurora-bloom-bg-flip.md +54 -54
- package/lib/motion/shotcraft/upstream/references/shots/effects/brand-frame-snap.md +58 -58
- package/lib/motion/shotcraft/upstream/references/shots/effects/fui-hud-moves.md +35 -35
- package/lib/motion/shotcraft/upstream/references/shots/effects/glow-flyline-moves.md +53 -53
- package/lib/motion/shotcraft/upstream/references/shots/effects/icon-performance-moves.md +42 -42
- package/lib/motion/shotcraft/upstream/references/shots/effects/impact-feedback.md +50 -50
- package/lib/motion/shotcraft/upstream/references/shots/effects/light-play-moves.md +46 -46
- package/lib/motion/shotcraft/upstream/references/shots/effects/radial-ripple-phone-chips.md +56 -56
- package/lib/motion/shotcraft/upstream/references/shots/effects/riso-print-hits.md +49 -49
- package/lib/motion/shotcraft/upstream/references/shots/effects/scan-bracket-sweep.md +50 -50
- package/lib/motion/shotcraft/upstream/references/shots/effects/scanline-annotate-focus.md +53 -53
- package/lib/motion/shotcraft/upstream/references/shots/effects/scanline-assemble-flyin.md +53 -53
- package/lib/motion/shotcraft/upstream/references/shots/effects/slam-entrance-moves.md +55 -55
- package/lib/motion/shotcraft/upstream/references/shots/effects/spotlight-sweep-moves.md +52 -52
- package/lib/motion/shotcraft/upstream/references/shots/interaction/autolayout-gap-dial.md +60 -60
- package/lib/motion/shotcraft/upstream/references/shots/interaction/canvas-materialize-moves.md +57 -57
- package/lib/motion/shotcraft/upstream/references/shots/interaction/chip-grid-single-select-blackout.md +51 -51
- package/lib/motion/shotcraft/upstream/references/shots/interaction/collab-cursor-moves.md +41 -41
- package/lib/motion/shotcraft/upstream/references/shots/interaction/glass-pill-dictation-typing.md +53 -53
- package/lib/motion/shotcraft/upstream/references/shots/interaction/hashtag-to-pill-materialize.md +60 -60
- package/lib/motion/shotcraft/upstream/references/shots/interaction/picker-carousel-feature-cycle.md +55 -55
- package/lib/motion/shotcraft/upstream/references/shots/interaction/segmented-thumb-hero.md +57 -57
- package/lib/motion/shotcraft/upstream/references/shots/interaction/theme-switch-moves.md +47 -47
- package/lib/motion/shotcraft/upstream/references/shots/interaction/type-and-filter.md +37 -37
- package/lib/motion/shotcraft/upstream/references/shots/interaction/voice-waveform-live.md +54 -54
- package/lib/motion/shotcraft/upstream/references/shots/opening/brand-ink-open.md +36 -36
- package/lib/motion/shotcraft/upstream/references/shots/opening/crane-rise-reveal.md +56 -56
- package/lib/motion/shotcraft/upstream/references/shots/opening/dataviz-landscape-open.md +56 -56
- package/lib/motion/shotcraft/upstream/references/shots/opening/fracture.md +40 -40
- package/lib/motion/shotcraft/upstream/references/shots/opening/icon-field-colorize.md +60 -60
- package/lib/motion/shotcraft/upstream/references/shots/opening/letterspace-materialize.md +57 -57
- package/lib/motion/shotcraft/upstream/references/shots/opening/magician-card-flourish.md +73 -73
- package/lib/motion/shotcraft/upstream/references/shots/opening/orbit-ring-title-open.md +87 -87
- package/lib/motion/shotcraft/upstream/references/shots/opening/spotlight-hero-card.md +55 -55
- package/lib/motion/shotcraft/upstream/references/shots/opening/stroke-segment-build.md +50 -50
- package/lib/motion/shotcraft/upstream/references/shots/opening/text-as-mask.md +59 -59
- package/lib/motion/shotcraft/upstream/references/shots/outro/grain-dissolve.md +38 -38
- package/lib/motion/shotcraft/upstream/references/shots/outro/logo-shrink-wordmark-lockup.md +38 -38
- package/lib/motion/shotcraft/upstream/references/shots/outro/neon-triple-marquee.md +59 -59
- package/lib/motion/shotcraft/upstream/references/shots/outro/outro-group-photo-launch.md +39 -39
- package/lib/motion/shotcraft/upstream/references/shots/outro/ui-strip-away-outro.md +61 -61
- package/lib/motion/shotcraft/upstream/references/shots/outro/ui-to-brand-morph.md +52 -52
- package/lib/motion/shotcraft/upstream/references/shots/rhythm/beat-cut-moves.md +50 -50
- package/lib/motion/shotcraft/upstream/references/shots/rhythm/beat-step-list-theme-cycle.md +66 -66
- package/lib/motion/shotcraft/upstream/references/shots/rhythm/panel-grid-moves.md +43 -43
- package/lib/motion/shotcraft/upstream/references/shots/rhythm/quad-split-parallel-scenes.md +38 -38
- package/lib/motion/shotcraft/upstream/references/shots/rhythm/rhythm-interrupt-moves.md +38 -38
- package/lib/motion/shotcraft/upstream/references/shots/rhythm/sakuga-timing-shift.md +56 -56
- package/lib/motion/shotcraft/upstream/references/shots/rhythm/speed-ramp-freeze.md +44 -44
- package/lib/motion/shotcraft/upstream/references/shots/rhythm/trailer-grammar-moves.md +38 -38
- package/lib/motion/shotcraft/upstream/references/shots/transition/bottom-push-stack-wipe.md +63 -63
- package/lib/motion/shotcraft/upstream/references/shots/transition/card-flip-reveal.md +57 -57
- package/lib/motion/shotcraft/upstream/references/shots/transition/card-flock-tumble.md +63 -63
- package/lib/motion/shotcraft/upstream/references/shots/transition/circle-match-iris.md +58 -58
- package/lib/motion/shotcraft/upstream/references/shots/transition/gradient-transition.md +45 -45
- package/lib/motion/shotcraft/upstream/references/shots/transition/line-carry-transition.md +55 -55
- package/lib/motion/shotcraft/upstream/references/shots/transition/mosaic-reframe.md +49 -49
- package/lib/motion/shotcraft/upstream/references/shots/transition/page-turn-transitions.md +42 -42
- package/lib/motion/shotcraft/upstream/references/shots/transition/print-texture-transitions.md +42 -42
- package/lib/motion/shotcraft/upstream/references/shots/transition/shot-transitions.md +45 -45
- package/lib/motion/shotcraft/upstream/references/shots/transition/tear-streak-transitions.md +48 -48
- package/lib/motion/shotcraft/upstream/references/shots/transition/transition-hidden-cut.md +55 -55
- package/lib/motion/shotcraft/upstream/references/shots/transition/transition-travel.md +43 -43
- package/lib/motion/shotcraft/upstream/references/shots/transition/white-flash-logo-simplify-cut.md +41 -41
- package/lib/motion/shotcraft/upstream/references/shots/transition/wipe-transitions.md +42 -42
- package/lib/motion/shotcraft/upstream/references/shots/typography/blur-slide.md +59 -59
- package/lib/motion/shotcraft/upstream/references/shots/typography/brace-expand.md +55 -55
- package/lib/motion/shotcraft/upstream/references/shots/typography/countdown-arc-scatter.md +65 -65
- package/lib/motion/shotcraft/upstream/references/shots/typography/document-typewriter-reveal.md +42 -42
- package/lib/motion/shotcraft/upstream/references/shots/typography/flying-words.md +64 -64
- package/lib/motion/shotcraft/upstream/references/shots/typography/glitch-cycle.md +64 -64
- package/lib/motion/shotcraft/upstream/references/shots/typography/gradient-word-sweep.md +54 -54
- package/lib/motion/shotcraft/upstream/references/shots/typography/lead-word-zoom-assemble.md +91 -91
- package/lib/motion/shotcraft/upstream/references/shots/typography/marker-underline-title.md +51 -51
- package/lib/motion/shotcraft/upstream/references/shots/typography/outline-word-fill.md +66 -66
- package/lib/motion/shotcraft/upstream/references/shots/typography/paper-title-card.md +38 -38
- package/lib/motion/shotcraft/upstream/references/shots/typography/pill-chip-slot-cycle-handled.md +66 -66
- package/lib/motion/shotcraft/upstream/references/shots/typography/pill-slot-cycle.md +58 -58
- package/lib/motion/shotcraft/upstream/references/shots/typography/text-column-converge.md +61 -61
- package/lib/motion/shotcraft/upstream/references/shots/typography/title-demote-to-label.md +64 -64
- package/lib/motion/shotcraft/upstream/references/shots/typography/type-assembly-moves.md +45 -45
- package/lib/motion/shotcraft/upstream/references/shots/typography/type-entrance-moves.md +41 -41
- package/lib/motion/shotcraft/upstream/references/shots/typography/type-rhythm-sync.md +51 -51
- package/lib/motion/shotcraft/upstream/references/shots/typography/typewriter-moves.md +42 -42
- package/lib/motion/shotcraft/upstream/references/shots/typography/vertical-word-roll-blur-cycle.md +63 -63
- package/lib/motion/shotcraft/upstream/references/shots/typography/word-relay-filmstrip.md +57 -57
- package/lib/motion/shotcraft/upstream/references/shots/typography/word-relay-geometry.md +75 -75
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/avatar-bracket-carousel.md +59 -59
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/bezier-source-converge-merge.md +67 -67
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/card-stack.md +60 -60
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/carousel-3d.md +66 -66
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/cloner-depth-echo.md +40 -40
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/deck-deal-flyin.md +45 -45
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/doc-park-left-pill-deal.md +62 -62
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/draw-svg-trace.md +55 -55
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/element-body-moves.md +37 -37
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/integration-hub-map.md +55 -55
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/list-reveal.md +50 -50
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/list-stack-press.md +43 -43
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/neon-frame-forerun.md +55 -55
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/neon-frame-orbit-drop.md +51 -51
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/page-waterfall-wall.md +51 -51
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/product-card-progressive-assemble.md +56 -56
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/radial-wave.md +49 -49
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/research-card-stack-scroll.md +57 -57
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/row-embed.md +36 -36
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/value-stagger-gradient.md +64 -64
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/wall-reveal-moves.md +47 -47
- package/lib/motion/shotcraft/upstream/references/sound-design.md +170 -170
- package/lib/motion/shotcraft/upstream/references/workbench.md +157 -157
- package/lib/motion/shotcraft/upstream/template/TEMPLATE.md +103 -103
- package/lib/motion/shotcraft/upstream/template/THEMES.md +44 -44
- package/lib/motion/shotcraft/upstream/template/package.json +20 -20
- package/lib/motion/shotcraft/upstream/template/remotion.config.ts +6 -6
- package/lib/motion/shotcraft/upstream/template/src/Root.tsx +17 -17
- package/lib/motion/shotcraft/upstream/template/src/aifl/Caption.tsx +65 -65
- package/lib/motion/shotcraft/upstream/template/src/aifl/DigitRoll.tsx +48 -48
- package/lib/motion/shotcraft/upstream/template/src/aifl/FlashCut.tsx +24 -24
- package/lib/motion/shotcraft/upstream/template/src/aifl/Main.tsx +192 -192
- package/lib/motion/shotcraft/upstream/template/src/aifl/PaperTitleCard.tsx +111 -111
- package/lib/motion/shotcraft/upstream/template/src/aifl/live/PageCam.tsx +150 -150
- package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneDetail.tsx +158 -158
- package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneFlyIn.tsx +468 -468
- package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneOpen.tsx +552 -552
- package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneOutroLive.tsx +411 -411
- package/lib/motion/shotcraft/upstream/template/src/aifl/live/ScenePapers.tsx +200 -200
- package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneWbr.tsx +324 -324
- package/lib/motion/shotcraft/upstream/template/src/aifl/live-layout.json +388 -388
- package/lib/motion/shotcraft/upstream/template/src/index.ts +4 -4
- package/lib/motion/shotcraft/upstream/template/src/themes/ThemedFilm.tsx +7 -7
- package/lib/motion/shotcraft/upstream/template/src/themes/manifest.tsx +68 -68
- package/lib/motion/shotcraft/upstream/template/src/themes/palette-assets.json +1 -1
- package/lib/motion/shotcraft/upstream/template/src/themes/palette-assets.ts +24 -24
- package/lib/motion/shotcraft/upstream/template/src/themes/palettes.json +74 -74
- package/lib/motion/shotcraft/upstream/template/src/themes/visual-theme.tsx +91 -91
- package/lib/motion/shotcraft/upstream/template/src/workbench.ts +213 -213
- package/lib/motion/shotcraft/upstream/template/tsconfig.json +15 -15
- package/lib/motion/style-frames.js +306 -306
- package/lib/motion/sync.js +214 -214
- package/lib/motion-library.js +1 -1
- package/lib/motion-project.js +105 -105
- package/lib/motion-render-worker.js +174 -174
- package/lib/routing/router.js +222 -222
- package/lib/routing/vendor/README.md +96 -96
- package/lib/routing/vendor/libavoid-routing.js +753 -753
- package/lib/routing/vendor/libavoid.min.js +59 -59
- package/lib/svg-export.js +1344 -0
- package/lib/video-trace-pool.js +128 -128
- package/package.json +3 -2
- package/LICENSE +0 -4175
|
@@ -1,647 +1,647 @@
|
|
|
1
|
-
// 标准占位素材包(孵化轮 PLAYBOOK ③)——卡片组 + 假 dashboard 面板 + 标题字块。
|
|
2
|
-
// Phase 0 质感升级后:外形几何与导出签名完全不变(Card 填满 w×h、同 padding 盒;
|
|
3
|
-
// FakeDashboard 1920×1080、侧栏 220、顶栏 72、A=3×2 网格 pad36/gap28、B=5 行 pad36/gap20;
|
|
4
|
-
// TitleBlock 默认 88 号),只把渲染从灰阶骨架条换成克制的出版级产品 UI:发丝线细边、
|
|
5
|
-
// 两层软阴影 + 顶部内高光、真实感假内容(指标数字 / sparkline / 首字母头像 / 状态 chip /
|
|
6
|
-
// 内联 SVG 图标)、系统字体栈与字重层级。中性暖灰 + 1 个安静的强调色,永远不抢运动的戏。
|
|
7
|
-
// 全部内容由 seed 确定性生成(无随机源)。可选 tone="dark" 出同布局深色版(换肤类 demo 用)。
|
|
8
|
-
// 依赖 react + ./Brand(侧栏/顶栏的 video-shotcraft 标志与名字);copy demo 时一并带上这两个文件并改 import 路径。
|
|
9
|
-
import React from 'react';
|
|
10
|
-
import { ShotcraftMark } from './Brand';
|
|
11
|
-
|
|
12
|
-
const SANS = '-apple-system, BlinkMacSystemFont, "SF Pro Display", "Helvetica Neue", Inter, Arial, sans-serif';
|
|
13
|
-
const MONO = '"SF Mono", "JetBrains Mono", Menlo, monospace';
|
|
14
|
-
|
|
15
|
-
// 调色板:前 10 个键是历史值,demo 大量直接引用,键名与取值永远不改;
|
|
16
|
-
// 后面是 Phase 0 新增的精致 UI 令牌,per-demo 自绘元素可直接取用。
|
|
17
|
-
export const G = {
|
|
18
|
-
bg: '#ececea',
|
|
19
|
-
panel: '#f7f7f6',
|
|
20
|
-
line: '#dcdcda',
|
|
21
|
-
bar: '#c2c2c0',
|
|
22
|
-
ink: '#2f2f2f',
|
|
23
|
-
mid: '#8f8f8d',
|
|
24
|
-
card: '#ffffff',
|
|
25
|
-
border: '#d8d8d6',
|
|
26
|
-
side: '#3a3a3a',
|
|
27
|
-
sideBar: '#5a5a58',
|
|
28
|
-
// —— 新增令牌 ——
|
|
29
|
-
font: SANS, // 系统无衬线字体栈
|
|
30
|
-
mono: MONO, // 等宽字体栈
|
|
31
|
-
canvas: '#f1f1ef', // dashboard 内容区底色(比 bg 略亮)
|
|
32
|
-
ink1: '#17181c', // 主文字(带一点冷调的近黑,替代纯黑)
|
|
33
|
-
ink2: '#5d5f66', // 次级文字
|
|
34
|
-
ink3: '#9b9da3', // 三级文字 / 占位提示
|
|
35
|
-
fill: '#f3f3f1', // 浅填充(chip / 图标底 / 输入框)
|
|
36
|
-
fill2: '#e7e7e4', // 中填充(图表底色 / 未激活柱)
|
|
37
|
-
hairline: 'rgba(20,22,28,0.08)', // 1px 发丝线颜色(用法:border: `1px solid ${G.hairline}`)
|
|
38
|
-
hairlineStrong: 'rgba(20,22,28,0.13)', // 稍强的发丝线(输入框 / 分隔)
|
|
39
|
-
accent: '#5b63d3', // 唯一强调色:安静的靛蓝
|
|
40
|
-
accentSoft: 'rgba(91,99,211,0.11)', // 强调色浅底(chip / 选中态)
|
|
41
|
-
dark: '#111216', // 带色相的深色场景底(替代 #000)
|
|
42
|
-
darkSurface: '#18191e', // 深色场景里的卡片面
|
|
43
|
-
shadowSm: 'inset 0 1px 0 rgba(255,255,255,0.85), 0 1px 2px rgba(16,18,24,0.06), 0 6px 18px -6px rgba(16,18,24,0.10)',
|
|
44
|
-
shadowMd: 'inset 0 1px 0 rgba(255,255,255,0.85), 0 2px 4px rgba(16,18,24,0.06), 0 16px 40px -12px rgba(16,18,24,0.18)',
|
|
45
|
-
shadowLg: 'inset 0 1px 0 rgba(255,255,255,0.85), 0 4px 10px rgba(16,18,24,0.08), 0 36px 80px -20px rgba(16,18,24,0.30)',
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
type Tone = 'light' | 'dark';
|
|
49
|
-
|
|
50
|
-
// 内部双主题令牌(light 与 G 新增键一致;dark 为同布局深色版)
|
|
51
|
-
const P = {
|
|
52
|
-
light: {
|
|
53
|
-
canvas: 'linear-gradient(180deg, #f4f4f2 0%, #eeeeeb 100%)',
|
|
54
|
-
surface: G.card, raised: '#fafaf9', line: G.hairline, line2: G.hairlineStrong,
|
|
55
|
-
ink: G.ink1, ink2: G.ink2, ink3: G.ink3, fill: G.fill, fill2: G.fill2,
|
|
56
|
-
chart: '#dcdde1', chart2: '#c4c6cc', accent: G.accent, accentSoft: G.accentSoft,
|
|
57
|
-
side: 'linear-gradient(180deg, #1c1d22 0%, #16171b 100%)', sideLine: 'rgba(255,255,255,0.06)',
|
|
58
|
-
sideInk: 'rgba(255,255,255,0.94)', sideInk2: 'rgba(255,255,255,0.56)', sideInk3: 'rgba(255,255,255,0.32)',
|
|
59
|
-
sideFill: 'rgba(255,255,255,0.075)', ring: '#ffffff',
|
|
60
|
-
shadow: G.shadowSm, avatar: ['#e4e4e1', '#d9dade', '#e9e6df', '#dfe1e8'], avatarInk: '#4a4c53',
|
|
61
|
-
},
|
|
62
|
-
dark: {
|
|
63
|
-
canvas: 'linear-gradient(180deg, #121318 0%, #0e0f13 100%)',
|
|
64
|
-
surface: G.darkSurface, raised: '#141519', line: 'rgba(255,255,255,0.07)', line2: 'rgba(255,255,255,0.12)',
|
|
65
|
-
ink: '#ededf0', ink2: '#a3a5ad', ink3: '#6c6e76', fill: 'rgba(255,255,255,0.05)', fill2: 'rgba(255,255,255,0.08)',
|
|
66
|
-
chart: 'rgba(255,255,255,0.10)', chart2: 'rgba(255,255,255,0.18)', accent: '#8088f0', accentSoft: 'rgba(128,136,240,0.16)',
|
|
67
|
-
side: 'linear-gradient(180deg, #0c0d10 0%, #0a0b0e 100%)', sideLine: 'rgba(255,255,255,0.06)',
|
|
68
|
-
sideInk: 'rgba(255,255,255,0.94)', sideInk2: 'rgba(255,255,255,0.52)', sideInk3: 'rgba(255,255,255,0.28)',
|
|
69
|
-
sideFill: 'rgba(255,255,255,0.07)', ring: G.darkSurface,
|
|
70
|
-
shadow: 'inset 0 1px 0 rgba(255,255,255,0.05), 0 1px 2px rgba(0,0,0,0.35), 0 8px 24px -8px rgba(0,0,0,0.55)',
|
|
71
|
-
avatar: ['#2b2c33', '#33343b', '#2a2d38', '#30302f'], avatarInk: '#c9cad0',
|
|
72
|
-
},
|
|
73
|
-
};
|
|
74
|
-
type Pal = typeof P.light;
|
|
75
|
-
|
|
76
|
-
// —— 确定性小工具 ——
|
|
77
|
-
const hash = (n: number) => {
|
|
78
|
-
const x = Math.sin(n * 127.1 + 311.7) * 43758.5453;
|
|
79
|
-
return x - Math.floor(x);
|
|
80
|
-
};
|
|
81
|
-
const pick = <T,>(arr: T[], i: number) => arr[((i % arr.length) + arr.length) % arr.length];
|
|
82
|
-
const fmt = (n: number) => Math.round(n).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
83
|
-
// 平滑的伪数据序列:两组正弦 + 趋势 + 微噪,归一化到 [0,1]
|
|
84
|
-
const series = (seed: number, n: number, trend = 0.35) => {
|
|
85
|
-
const raw = Array.from({ length: n }, (_, i) =>
|
|
86
|
-
trend * (i / (n - 1)) +
|
|
87
|
-
0.15 * Math.sin(i * 0.52 + seed * 1.7) +
|
|
88
|
-
0.06 * Math.sin(i * 1.35 + seed * 0.6) +
|
|
89
|
-
0.04 * (hash(seed * 31 + i) - 0.5),
|
|
90
|
-
);
|
|
91
|
-
const lo = Math.min(...raw);
|
|
92
|
-
const hi = Math.max(...raw);
|
|
93
|
-
return raw.map((v) => (v - lo) / (hi - lo || 1));
|
|
94
|
-
};
|
|
95
|
-
// Catmull-Rom → 三次贝塞尔,折线变顺滑曲线
|
|
96
|
-
const smooth = (pts: [number, number][]) => {
|
|
97
|
-
let d = `M${pts[0][0].toFixed(1)},${pts[0][1].toFixed(1)}`;
|
|
98
|
-
for (let i = 0; i < pts.length - 1; i++) {
|
|
99
|
-
const p0 = pts[Math.max(0, i - 1)];
|
|
100
|
-
const p1 = pts[i];
|
|
101
|
-
const p2 = pts[i + 1];
|
|
102
|
-
const p3 = pts[Math.min(pts.length - 1, i + 2)];
|
|
103
|
-
const c1x = p1[0] + (p2[0] - p0[0]) / 6;
|
|
104
|
-
const c1y = p1[1] + (p2[1] - p0[1]) / 6;
|
|
105
|
-
const c2x = p2[0] - (p3[0] - p1[0]) / 6;
|
|
106
|
-
const c2y = p2[1] - (p3[1] - p1[1]) / 6;
|
|
107
|
-
d += ` C${c1x.toFixed(1)},${c1y.toFixed(1)} ${c2x.toFixed(1)},${c2y.toFixed(1)} ${p2[0].toFixed(1)},${p2[1].toFixed(1)}`;
|
|
108
|
-
}
|
|
109
|
-
return d;
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
// —— 16×16 线性图标(1.5 描边,圆头圆角)——
|
|
113
|
-
const ICONS: Record<string, string[]> = {
|
|
114
|
-
home: ['M2.75 7.25 8 3l5.25 4.25', 'M4.25 6.25v6.5h7.5v-6.5'],
|
|
115
|
-
chart: ['M3 13V9', 'M6.5 13V4.5', 'M10 13V7', 'M13.5 13V3'],
|
|
116
|
-
folder: ['M2.5 4.75c0-.7.55-1.25 1.25-1.25h2.6l1.5 1.5h4.4c.7 0 1.25.55 1.25 1.25v5.5c0 .7-.55 1.25-1.25 1.25h-8.5c-.7 0-1.25-.55-1.25-1.25z'],
|
|
117
|
-
users: ['M6 7.25a2.25 2.25 0 1 0 0-4.5 2.25 2.25 0 0 0 0 4.5z', 'M2 13c.5-2.1 2-3.25 4-3.25S9.5 10.9 10 13', 'M10.5 2.9a2.25 2.25 0 0 1 0 4.2', 'M12 9.9c1 .5 1.7 1.5 2 3.1'],
|
|
118
|
-
file: ['M4.25 2.5h4.5l3 3v7.25c0 .4-.35.75-.75.75h-6.75a.75.75 0 0 1-.75-.75v-9.5c0-.4.35-.75.75-.75z', 'M8.75 2.5v3h3', 'M5.75 9h4.5', 'M5.75 11.25h3'],
|
|
119
|
-
card: ['M3.75 3.75h8.5c.7 0 1.25.55 1.25 1.25v6c0 .7-.55 1.25-1.25 1.25h-8.5c-.7 0-1.25-.55-1.25-1.25v-6c0-.7.55-1.25 1.25-1.25z', 'M2.5 6.75h11', 'M4.75 9.5h2'],
|
|
120
|
-
sliders: ['M3 4.5h5.5', 'M11.5 4.5H13', 'M3 11.5h1.5', 'M7.5 11.5H13', 'M10 3v3', 'M6 10v3'],
|
|
121
|
-
search: ['M7 11.5a4.5 4.5 0 1 0 0-9 4.5 4.5 0 0 0 0 9z', 'm10.5 10.5 3 3'],
|
|
122
|
-
bolt: ['M8.75 2 3.75 9h4l-.5 5 5-7h-4z'],
|
|
123
|
-
globe: ['M8 13.5a5.5 5.5 0 1 0 0-11 5.5 5.5 0 0 0 0 11z', 'M2.5 8h11', 'M8 2.5c1.6 1.6 2.25 3.5 2.25 5.5S9.6 11.9 8 13.5C6.4 11.9 5.75 10 5.75 8S6.4 4.1 8 2.5z'],
|
|
124
|
-
layers: ['M8 2.5 13.5 5.5 8 8.5 2.5 5.5z', 'm2.5 8.25 5.5 3 5.5-3', 'm2.5 10.75 5.5 3 5.5-3'],
|
|
125
|
-
pulse: ['M2 8.5h2.5l1.5-4 3 8 1.5-4H14'],
|
|
126
|
-
target: ['M8 13.5a5.5 5.5 0 1 0 0-11 5.5 5.5 0 0 0 0 11z', 'M8 10.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z'],
|
|
127
|
-
grid: ['M3 3h4v4H3z', 'M9 3h4v4H9z', 'M3 9h4v4H3z', 'M9 9h4v4H9z'],
|
|
128
|
-
inbox: ['M2.5 9h3l1 1.75h3l1-1.75h3', 'M4.25 3.5h7.5l1.75 5.5v3.25c0 .4-.35.75-.75.75h-9.5a.75.75 0 0 1-.75-.75V9z'],
|
|
129
|
-
arrowUR: ['M5 11 11 5', 'M6 5h5v5'],
|
|
130
|
-
arrowR: ['M3.5 8h9', 'M9 4.5 12.5 8 9 11.5'],
|
|
131
|
-
chevron: ['m6 4 4 4-4 4'],
|
|
132
|
-
clock: ['M8 13.5a5.5 5.5 0 1 0 0-11 5.5 5.5 0 0 0 0 11z', 'M8 5v3.25l2.25 1.5'],
|
|
133
|
-
bell: ['M4.5 6.5a3.5 3.5 0 0 1 7 0c0 3 1.25 4.25 1.25 4.25h-9.5S4.5 9.5 4.5 6.5z', 'M6.75 13a1.4 1.4 0 0 0 2.5 0'],
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
const Ic: React.FC<{ name: string; size?: number; color: string; sw?: number }> = ({ name, size = 16, color, sw = 1.5 }) => (
|
|
137
|
-
<svg width={size} height={size} viewBox="0 0 16 16" fill="none" stroke={color} strokeWidth={sw}
|
|
138
|
-
strokeLinecap="round" strokeLinejoin="round" style={{ display: 'block', flex: 'none' }}>
|
|
139
|
-
{(ICONS[name] ?? []).map((d, i) => <path key={i} d={d} />)}
|
|
140
|
-
</svg>
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
const More: React.FC<{ color: string; size?: number }> = ({ color, size = 16 }) => (
|
|
144
|
-
<svg width={size} height={size} viewBox="0 0 16 16" style={{ display: 'block', flex: 'none' }}>
|
|
145
|
-
{[3.5, 8, 12.5].map((x) => <circle key={x} cx={x} cy={8} r={1.25} fill={color} />)}
|
|
146
|
-
</svg>
|
|
147
|
-
);
|
|
148
|
-
|
|
149
|
-
const INITIALS = ['AK', 'MR', 'JL', 'SO', 'TN', 'EV', 'DP', 'CW'];
|
|
150
|
-
const Avatar: React.FC<{ i: number; size: number; p: Pal; ring?: boolean }> = ({ i, size, p, ring }) => (
|
|
151
|
-
<div style={{
|
|
152
|
-
width: size, height: size, borderRadius: size / 2, flex: 'none', boxSizing: 'border-box',
|
|
153
|
-
background: pick(p.avatar, i), color: p.avatarInk, display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
154
|
-
fontSize: size * 0.36, fontWeight: 600, letterSpacing: '0.01em',
|
|
155
|
-
boxShadow: ring ? `0 0 0 2px ${p.ring}` : `inset 0 0 0 1px ${p.line}`,
|
|
156
|
-
}}>
|
|
157
|
-
{pick(INITIALS, i)}
|
|
158
|
-
</div>
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
const Chip: React.FC<{ p: Pal; children: React.ReactNode; accent?: boolean; dot?: boolean; size?: number }> = ({ p, children, accent, dot, size = 11.5 }) => (
|
|
162
|
-
<div style={{
|
|
163
|
-
display: 'flex', alignItems: 'center', gap: size * 0.45, flex: 'none', whiteSpace: 'nowrap',
|
|
164
|
-
padding: `${size * 0.28}px ${size * 0.62}px`, borderRadius: size * 0.55, fontSize: size, fontWeight: 500,
|
|
165
|
-
lineHeight: 1.2, color: accent ? p.accent : p.ink2, background: accent ? p.accentSoft : p.fill,
|
|
166
|
-
boxShadow: accent ? 'none' : `inset 0 0 0 1px ${p.line}`,
|
|
167
|
-
}}>
|
|
168
|
-
{dot && <div style={{ width: size * 0.5, height: size * 0.5, borderRadius: size, background: accent ? p.accent : p.ink3 }} />}
|
|
169
|
-
{children}
|
|
170
|
-
</div>
|
|
171
|
-
);
|
|
172
|
-
|
|
173
|
-
// —— 卡片内容表(seed 决定类型与文案;全部虚构、通用)——
|
|
174
|
-
type Kind = 'area' | 'list' | 'bars' | 'progress' | 'heat';
|
|
175
|
-
const KINDS: Kind[] = ['area', 'list', 'bars', 'progress', 'heat'];
|
|
176
|
-
const COPY: Record<Kind, { title: string; icon: string; value: string; delta?: string; caption: string; chip: string }[]> = {
|
|
177
|
-
area: [
|
|
178
|
-
{ title: 'Active users', icon: 'users', value: '24,812', delta: '+12.4%', caption: 'vs. previous 7 days', chip: '7d' },
|
|
179
|
-
{ title: 'Sessions', icon: 'pulse', value: '182,340', delta: '+8.1%', caption: 'vs. previous 30 days', chip: '30d' },
|
|
180
|
-
{ title: 'API requests', icon: 'bolt', value: '1.20M', delta: '+3.6%', caption: 'p95 latency 148 ms', chip: '24h' },
|
|
181
|
-
],
|
|
182
|
-
list: [
|
|
183
|
-
{ title: 'Top pages', icon: 'globe', value: '', caption: '', chip: 'Live' },
|
|
184
|
-
{ title: 'Recent activity', icon: 'inbox', value: '', caption: '', chip: 'Today' },
|
|
185
|
-
],
|
|
186
|
-
bars: [
|
|
187
|
-
{ title: 'Revenue', icon: 'chart', value: '$48.2k', delta: '+6.3%', caption: 'Net volume this month', chip: 'Monthly' },
|
|
188
|
-
{ title: 'Orders', icon: 'card', value: '3,912', delta: '+4.8%', caption: 'Across all channels', chip: 'Weekly' },
|
|
189
|
-
],
|
|
190
|
-
progress: [
|
|
191
|
-
{ title: 'Quarterly goals', icon: 'target', value: '68%', caption: 'of Q3 target reached', chip: 'Q3' },
|
|
192
|
-
{ title: 'Sprint 24', icon: 'layers', value: '74%', caption: '31 of 42 issues closed', chip: '6d left' },
|
|
193
|
-
],
|
|
194
|
-
heat: [
|
|
195
|
-
{ title: 'Deploys', icon: 'grid', value: '1,284', caption: 'in the last 12 weeks', chip: '12w' },
|
|
196
|
-
{ title: 'Activity', icon: 'grid', value: '412', caption: 'events this quarter', chip: 'Q3' },
|
|
197
|
-
],
|
|
198
|
-
};
|
|
199
|
-
const PAGES = ['/pricing', '/docs/getting-started', '/changelog', '/blog/release-notes', '/integrations', '/careers'];
|
|
200
|
-
const EVENTS = ['merged #482 into main', 'commented on Roadmap', 'deployed v2.14.0', 'opened Billing v2', 'closed 3 issues', 'invited 2 members'];
|
|
201
|
-
const GOALS = ['Design system', 'Mobile app', 'Billing v2', 'Docs refresh', 'Search'];
|
|
202
|
-
|
|
203
|
-
// 卡片设计基准:网格卡 524×454 的内容区(减去 border+padding 20)
|
|
204
|
-
const BASE_W = 484;
|
|
205
|
-
const BASE_H = 414;
|
|
206
|
-
const HEADER = 28;
|
|
207
|
-
const GAP = 16;
|
|
208
|
-
const METRIC = 64;
|
|
209
|
-
const FOOTER = 54;
|
|
210
|
-
|
|
211
|
-
const Body: React.FC<{ kind: Kind; w: number; h: number; seed: number; p: Pal; id: string }> = ({ kind, w, h, seed, p, id }) => {
|
|
212
|
-
if (kind === 'area') {
|
|
213
|
-
const n = 26;
|
|
214
|
-
const v = series(seed, n, 0.42);
|
|
215
|
-
const prev = series(seed + 11, n, 0.1);
|
|
216
|
-
const yOf = (x: number) => 8 + (1 - x) * (h - 16);
|
|
217
|
-
const pts = v.map((x, i) => [(i / (n - 1)) * (w - 6), yOf(x * 0.82 + 0.1)] as [number, number]);
|
|
218
|
-
const ppts = prev.map((x, i) => [(i / (n - 1)) * (w - 6), yOf(x * 0.5 + 0.12)] as [number, number]);
|
|
219
|
-
const line = smooth(pts);
|
|
220
|
-
const last = pts[n - 1];
|
|
221
|
-
return (
|
|
222
|
-
<svg width={w} height={h} style={{ display: 'block', overflow: 'visible' }}>
|
|
223
|
-
<defs>
|
|
224
|
-
<linearGradient id={id} x1="0" y1="0" x2="0" y2="1">
|
|
225
|
-
<stop offset="0" stopColor={p.accent} stopOpacity={0.2} />
|
|
226
|
-
<stop offset="1" stopColor={p.accent} stopOpacity={0} />
|
|
227
|
-
</linearGradient>
|
|
228
|
-
</defs>
|
|
229
|
-
{[0.12, 0.5, 0.88].map((t) => (
|
|
230
|
-
<line key={t} x1={0} x2={w} y1={h * t} y2={h * t} stroke={p.line} strokeWidth={1} strokeDasharray="2 5" />
|
|
231
|
-
))}
|
|
232
|
-
<path d={smooth(ppts)} fill="none" stroke={p.chart2} strokeWidth={1.5} strokeDasharray="3 4" />
|
|
233
|
-
<path d={`${line} L${last[0].toFixed(1)},${h} L0,${h} Z`} fill={`url(#${id})`} />
|
|
234
|
-
<path d={line} fill="none" stroke={p.accent} strokeWidth={2.25} strokeLinecap="round" />
|
|
235
|
-
<circle cx={last[0]} cy={last[1]} r={9} fill={p.accent} opacity={0.14} />
|
|
236
|
-
<circle cx={last[0]} cy={last[1]} r={4} fill={p.surface} stroke={p.accent} strokeWidth={2} />
|
|
237
|
-
</svg>
|
|
238
|
-
);
|
|
239
|
-
}
|
|
240
|
-
if (kind === 'bars') {
|
|
241
|
-
const n = Math.max(8, Math.min(24, Math.round(w / 34)));
|
|
242
|
-
const v = series(seed + 3, n, 0.3);
|
|
243
|
-
const labelH = h >= 96 ? 20 : 0;
|
|
244
|
-
const ch = h - labelH;
|
|
245
|
-
const slot = w / n;
|
|
246
|
-
const bw = slot * 0.56;
|
|
247
|
-
const peak = v.indexOf(Math.max(...v));
|
|
248
|
-
const step = Math.ceil(n / 6); // 每 step 根柱一个月份刻度
|
|
249
|
-
const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
250
|
-
return (
|
|
251
|
-
<svg width={w} height={h} style={{ display: 'block' }}>
|
|
252
|
-
<line x1={0} x2={w} y1={ch - 0.5} y2={ch - 0.5} stroke={p.line} strokeWidth={1} />
|
|
253
|
-
{v.map((x, i) => {
|
|
254
|
-
const bh = Math.max(4, (0.16 + x * 0.84) * (ch - 6));
|
|
255
|
-
return (
|
|
256
|
-
<rect key={i} x={i * slot + (slot - bw) / 2} y={ch - bh - 1} width={bw} height={bh} rx={Math.min(4, bw / 3)}
|
|
257
|
-
fill={i === peak ? p.accent : p.chart} />
|
|
258
|
-
);
|
|
259
|
-
})}
|
|
260
|
-
{labelH > 0 && v.map((_, i) => (i % step === 0 ? (
|
|
261
|
-
<text key={i} x={i * slot + slot / 2} y={h - 4} textAnchor="middle" fontSize={11} fill={p.ink3} fontFamily={SANS}>
|
|
262
|
-
{MONTHS[(i / step + 7) % 12]}
|
|
263
|
-
</text>
|
|
264
|
-
) : null))}
|
|
265
|
-
</svg>
|
|
266
|
-
);
|
|
267
|
-
}
|
|
268
|
-
if (kind === 'list') {
|
|
269
|
-
const rowH = 42;
|
|
270
|
-
const rows = Math.max(1, Math.min(6, Math.floor(h / rowH)));
|
|
271
|
-
const pages = seed % 2 === 0;
|
|
272
|
-
return (
|
|
273
|
-
<div style={{ width: w, height: h, display: 'flex', flexDirection: 'column' }}>
|
|
274
|
-
{Array.from({ length: rows }).map((_, i) => {
|
|
275
|
-
const val = Math.round(9000 * Math.pow(0.62, i) * (0.8 + hash(seed * 7 + i) * 0.4));
|
|
276
|
-
const share = Math.pow(0.66, i);
|
|
277
|
-
return (
|
|
278
|
-
<div key={i} style={{
|
|
279
|
-
height: rowH, flex: 'none', display: 'flex', alignItems: 'center', gap: 10, position: 'relative',
|
|
280
|
-
borderTop: i === 0 ? 'none' : `1px solid ${p.line}`, boxSizing: 'border-box',
|
|
281
|
-
}}>
|
|
282
|
-
{pages ? (
|
|
283
|
-
<>
|
|
284
|
-
<div style={{ position: 'absolute', left: 0, top: 8, bottom: 8, width: `${share * 100}%`, borderRadius: 6, background: i === 0 ? p.accentSoft : p.fill }} />
|
|
285
|
-
<div style={{ position: 'relative', paddingLeft: 10, fontSize: 13.5, color: p.ink, fontWeight: 500, fontFamily: MONO, letterSpacing: '-0.01em', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', minWidth: 0 }}>
|
|
286
|
-
{pick(PAGES, i + seed)}
|
|
287
|
-
</div>
|
|
288
|
-
</>
|
|
289
|
-
) : (
|
|
290
|
-
<>
|
|
291
|
-
<Avatar i={i + seed} size={26} p={p} />
|
|
292
|
-
<div style={{ fontSize: 13.5, color: p.ink2, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', minWidth: 0 }}>
|
|
293
|
-
<span style={{ color: p.ink, fontWeight: 600 }}>{pick(INITIALS, i + seed)}</span>{' '}{pick(EVENTS, i + seed)}
|
|
294
|
-
</div>
|
|
295
|
-
</>
|
|
296
|
-
)}
|
|
297
|
-
<div style={{ position: 'relative', marginLeft: 'auto', paddingRight: 4, fontSize: 13, color: p.ink3, fontVariantNumeric: 'tabular-nums', whiteSpace: 'nowrap' }}>
|
|
298
|
-
{pages ? fmt(val) : `${2 + i * 7 + (seed % 5)}m`}
|
|
299
|
-
</div>
|
|
300
|
-
</div>
|
|
301
|
-
);
|
|
302
|
-
})}
|
|
303
|
-
</div>
|
|
304
|
-
);
|
|
305
|
-
}
|
|
306
|
-
if (kind === 'progress') {
|
|
307
|
-
const rowH = 46;
|
|
308
|
-
const rows = Math.max(1, Math.min(5, Math.floor(h / rowH)));
|
|
309
|
-
return (
|
|
310
|
-
<div style={{ width: w, height: h, display: 'flex', flexDirection: 'column', justifyContent: 'flex-start' }}>
|
|
311
|
-
{Array.from({ length: rows }).map((_, i) => {
|
|
312
|
-
const pct = Math.round(92 - i * 13 - hash(seed * 3 + i) * 12);
|
|
313
|
-
return (
|
|
314
|
-
<div key={i} style={{ height: rowH, flex: 'none', display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 8 }}>
|
|
315
|
-
<div style={{ display: 'flex', fontSize: 13, color: p.ink2 }}>
|
|
316
|
-
<span style={{ color: p.ink, fontWeight: 500 }}>{pick(GOALS, i + seed)}</span>
|
|
317
|
-
<span style={{ marginLeft: 'auto', fontVariantNumeric: 'tabular-nums', color: p.ink3 }}>{pct}%</span>
|
|
318
|
-
</div>
|
|
319
|
-
<div style={{ height: 6, borderRadius: 3, background: p.fill2, overflow: 'hidden' }}>
|
|
320
|
-
<div style={{ width: `${pct}%`, height: '100%', borderRadius: 3, background: i === 0 ? p.accent : p.chart2 }} />
|
|
321
|
-
</div>
|
|
322
|
-
</div>
|
|
323
|
-
);
|
|
324
|
-
})}
|
|
325
|
-
</div>
|
|
326
|
-
);
|
|
327
|
-
}
|
|
328
|
-
// heat:活动热力格(7 行 × N 列)
|
|
329
|
-
const gap = 5;
|
|
330
|
-
const cell = Math.max(6, Math.min(18, Math.floor((h - gap * 6) / 7)));
|
|
331
|
-
const cols = Math.max(4, Math.floor((w + gap) / (cell + gap)));
|
|
332
|
-
// 克制配色:低档用中性灰,只有高档才上强调色
|
|
333
|
-
const fills = [p.fill2, p.chart, p.accent, p.accent, p.accent];
|
|
334
|
-
const alphas = [1, 1, 0.32, 0.58, 0.9];
|
|
335
|
-
return (
|
|
336
|
-
<svg width={w} height={h} style={{ display: 'block' }}>
|
|
337
|
-
{Array.from({ length: cols * 7 }).map((_, k) => {
|
|
338
|
-
const c = Math.floor(k / 7);
|
|
339
|
-
const r = k % 7;
|
|
340
|
-
const lvl = Math.min(4, Math.floor(Math.pow(hash(seed * 13 + k), 1.7) * 3.3 + (c / cols) * 1.5));
|
|
341
|
-
return (
|
|
342
|
-
<rect key={k} x={c * (cell + gap)} y={r * (cell + gap)} width={cell} height={cell} rx={Math.min(4, cell / 4)}
|
|
343
|
-
fill={fills[lvl]} fillOpacity={alphas[lvl]} />
|
|
344
|
-
);
|
|
345
|
-
})}
|
|
346
|
-
</svg>
|
|
347
|
-
);
|
|
348
|
-
};
|
|
349
|
-
|
|
350
|
-
// 卡片内容(设计坐标 Wd×Hd,外层用 CSS zoom 放到实际尺寸——布局期放大,文字按目标尺寸栅格化)
|
|
351
|
-
const CardContent: React.FC<{ Wd: number; Hd: number; seed: number; p: Pal; tone: Tone }> = ({ Wd, Hd, seed, p, tone }) => {
|
|
352
|
-
const kind = pick(KINDS, seed - 1);
|
|
353
|
-
const c = pick(COPY[kind], Math.floor((seed - 1) / KINDS.length));
|
|
354
|
-
const hasMetric = kind !== 'list';
|
|
355
|
-
const wide = Wd > Hd * 2.4;
|
|
356
|
-
const showFooter = !wide && Hd >= 340;
|
|
357
|
-
const id = `fx-area-${tone}-${seed}`;
|
|
358
|
-
const stackH = HEADER + GAP + (hasMetric && !wide ? METRIC + GAP : 0) + (showFooter ? FOOTER : 0);
|
|
359
|
-
const bodyH = wide ? Hd - HEADER - GAP : Hd - stackH;
|
|
360
|
-
const metricW = wide && hasMetric ? Math.min(260, Wd * 0.36) : 0;
|
|
361
|
-
const bodyW = wide && hasMetric ? Wd - metricW - 28 : Wd;
|
|
362
|
-
const metric = hasMetric && (
|
|
363
|
-
<div style={{ flex: 'none', width: metricW || undefined }}>
|
|
364
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 10, height: 40 }}>
|
|
365
|
-
<div style={{ fontSize: 40, fontWeight: 650, color: p.ink, letterSpacing: '-0.035em', lineHeight: 1, fontVariantNumeric: 'tabular-nums' }}>
|
|
366
|
-
{c.value}
|
|
367
|
-
</div>
|
|
368
|
-
{c.delta && (
|
|
369
|
-
<Chip p={p} accent size={12}>
|
|
370
|
-
<Ic name="arrowUR" size={11} color={p.accent} sw={1.8} />
|
|
371
|
-
{c.delta}
|
|
372
|
-
</Chip>
|
|
373
|
-
)}
|
|
374
|
-
</div>
|
|
375
|
-
<div style={{ marginTop: 9, fontSize: 13, color: p.ink3, lineHeight: '15px', whiteSpace: 'nowrap' }}>{c.caption}</div>
|
|
376
|
-
</div>
|
|
377
|
-
);
|
|
378
|
-
return (
|
|
379
|
-
<div style={{ width: Wd, height: Hd, display: 'flex', flexDirection: 'column', fontFamily: SANS, color: p.ink }}>
|
|
380
|
-
<div style={{ height: HEADER, flex: 'none', display: 'flex', alignItems: 'center', gap: 10 }}>
|
|
381
|
-
<div style={{
|
|
382
|
-
width: 28, height: 28, borderRadius: 8, background: p.fill, boxShadow: `inset 0 0 0 1px ${p.line}`,
|
|
383
|
-
display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 'none',
|
|
384
|
-
}}>
|
|
385
|
-
<Ic name={c.icon} size={15} color={p.ink2} />
|
|
386
|
-
</div>
|
|
387
|
-
<div style={{ fontSize: 15, fontWeight: 600, letterSpacing: '-0.01em', whiteSpace: 'nowrap' }}>{c.title}</div>
|
|
388
|
-
<div style={{ marginLeft: 'auto' }} />
|
|
389
|
-
<Chip p={p} dot={kind === 'list'} accent={kind === 'list'}>{c.chip}</Chip>
|
|
390
|
-
<More color={p.ink3} />
|
|
391
|
-
</div>
|
|
392
|
-
<div style={{ height: GAP, flex: 'none' }} />
|
|
393
|
-
{wide ? (
|
|
394
|
-
<div style={{ display: 'flex', gap: 28, alignItems: 'flex-start', height: bodyH }}>
|
|
395
|
-
{metric}
|
|
396
|
-
{bodyH >= 36 && <Body kind={kind} w={bodyW} h={bodyH} seed={seed} p={p} id={id} />}
|
|
397
|
-
</div>
|
|
398
|
-
) : (
|
|
399
|
-
<>
|
|
400
|
-
{metric}
|
|
401
|
-
{hasMetric && <div style={{ height: GAP, flex: 'none' }} />}
|
|
402
|
-
{bodyH >= 36 && <Body kind={kind} w={Wd} h={bodyH} seed={seed} p={p} id={id} />}
|
|
403
|
-
</>
|
|
404
|
-
)}
|
|
405
|
-
{showFooter && (
|
|
406
|
-
<div style={{
|
|
407
|
-
marginTop: 'auto', height: FOOTER - 14, flex: 'none', borderTop: `1px solid ${p.line}`,
|
|
408
|
-
display: 'flex', alignItems: 'flex-end', gap: 10, boxSizing: 'border-box',
|
|
409
|
-
}}>
|
|
410
|
-
<div style={{ display: 'flex' }}>
|
|
411
|
-
{[0, 1, 2].map((j) => (
|
|
412
|
-
<div key={j} style={{ marginLeft: j ? -4 : 0 }}><Avatar i={seed + j * 3} size={24} p={p} ring /></div>
|
|
413
|
-
))}
|
|
414
|
-
</div>
|
|
415
|
-
<div style={{ fontSize: 12.5, color: p.ink3, lineHeight: '24px', whiteSpace: 'nowrap' }}>Updated {2 + (seed * 7) % 50}m ago</div>
|
|
416
|
-
<div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 5, fontSize: 12.5, fontWeight: 500, color: p.ink2, lineHeight: '24px' }}>
|
|
417
|
-
View <Ic name="arrowR" size={13} color={p.ink2} />
|
|
418
|
-
</div>
|
|
419
|
-
</div>
|
|
420
|
-
)}
|
|
421
|
-
</div>
|
|
422
|
-
);
|
|
423
|
-
};
|
|
424
|
-
|
|
425
|
-
export const Card: React.FC<{
|
|
426
|
-
w: number; h: number; seed?: number; style?: React.CSSProperties; tone?: Tone;
|
|
427
|
-
}> = ({ w, h, seed = 0, style, tone = 'light' }) => {
|
|
428
|
-
const p = P[tone];
|
|
429
|
-
// w/h 为 0(父级用 style 100% 撑满)时按 dashboard 网格卡 524×454 排内容
|
|
430
|
-
const W = w > 0 ? w : 524;
|
|
431
|
-
const H = h > 0 ? h : 454;
|
|
432
|
-
const pad = typeof style?.padding === 'number' ? style.padding : 19;
|
|
433
|
-
const innerW0 = Math.max(40, W - 2 * (pad + 1));
|
|
434
|
-
const innerH0 = Math.max(40, H - 2 * (pad + 1));
|
|
435
|
-
// 内容缩放系数:按面积等比(几何平均),小卡保底 0.62、大卡封顶 2.4;大卡额外内缩一点留白
|
|
436
|
-
const kOf = (iw: number, ih: number) =>
|
|
437
|
-
Math.min(Math.max(Math.sqrt((iw / BASE_W) * (ih / BASE_H)), 0.62), 2.4, iw / 300, ih / 110);
|
|
438
|
-
const extra = Math.max(0, kOf(innerW0, innerH0) - 1) * 12;
|
|
439
|
-
const innerW = innerW0 - 2 * extra;
|
|
440
|
-
const innerH = innerH0 - 2 * extra;
|
|
441
|
-
const k = kOf(innerW, innerH);
|
|
442
|
-
return (
|
|
443
|
-
<div style={{
|
|
444
|
-
// 外形与旧版一致:w×h、圆角 14、border+padding 合计 20 的内容内缩(2px 灰边 → 1px 发丝线 + 19 padding)
|
|
445
|
-
width: w, height: h, background: p.surface, border: `1px solid ${p.line}`,
|
|
446
|
-
borderRadius: 14, padding: 19, boxSizing: 'border-box',
|
|
447
|
-
display: 'flex', flexDirection: 'column', gap: 10, overflow: 'hidden',
|
|
448
|
-
boxShadow: p.shadow, ...style,
|
|
449
|
-
}}>
|
|
450
|
-
<div style={{ flex: 'none', margin: extra }}>
|
|
451
|
-
<div style={{ width: innerW / k, height: innerH / k, zoom: k }}>
|
|
452
|
-
<CardContent Wd={innerW / k} Hd={innerH / k} seed={seed} p={p} tone={tone} />
|
|
453
|
-
</div>
|
|
454
|
-
</div>
|
|
455
|
-
</div>
|
|
456
|
-
);
|
|
457
|
-
};
|
|
458
|
-
|
|
459
|
-
const NAV = [
|
|
460
|
-
['grid', 'Overview'], ['chart', 'Analytics'], ['folder', 'Projects'], ['users', 'Members'],
|
|
461
|
-
['file', 'Reports'], ['card', 'Billing'], ['sliders', 'Settings'],
|
|
462
|
-
];
|
|
463
|
-
const ROWS = [
|
|
464
|
-
{ icon: 'layers', title: 'Onboarding flow redesign', sub: 'Design · Updated 2h ago', status: 'In review', val: '12,480', unit: 'views', pct: 72, due: 'Due Oct 18' },
|
|
465
|
-
{ icon: 'bolt', title: 'Edge cache rollout', sub: 'Platform · Updated 5h ago', status: 'Live', val: '1.2M', unit: 'requests', pct: 100, due: 'Shipped Sep 30' },
|
|
466
|
-
{ icon: 'card', title: 'Billing v2 migration', sub: 'Payments · Updated yesterday', status: 'In progress', val: '3,912', unit: 'accounts', pct: 58, due: 'Due Nov 2' },
|
|
467
|
-
{ icon: 'globe', title: 'Docs search relaunch', sub: 'Content · Updated 2d ago', status: 'Draft', val: '846', unit: 'queries', pct: 31, due: 'Due Nov 20' },
|
|
468
|
-
{ icon: 'pulse', title: 'Realtime alerts', sub: 'Infra · Updated 3d ago', status: 'Planned', val: '214', unit: 'rules', pct: 12, due: 'Due Dec 8' },
|
|
469
|
-
];
|
|
470
|
-
|
|
471
|
-
// 列表行(variant B):外形同旧版(flex:1、圆角 14、gap 24、内容左缘 28+2)
|
|
472
|
-
const ListRow: React.FC<{ i: number; p: Pal }> = ({ i, p }) => {
|
|
473
|
-
const r = ROWS[i % ROWS.length];
|
|
474
|
-
const live = r.status === 'Live';
|
|
475
|
-
const v = series(i * 5 + 2, 18, 0.3);
|
|
476
|
-
const sw = 200;
|
|
477
|
-
const sh = 40;
|
|
478
|
-
const spark = smooth(v.map((x, j) => [(j / 17) * sw, 4 + (1 - x) * (sh - 8)] as [number, number]));
|
|
479
|
-
return (
|
|
480
|
-
<div style={{
|
|
481
|
-
flex: 1, background: p.surface, border: `1px solid ${p.line}`, borderRadius: 14, display: 'flex',
|
|
482
|
-
alignItems: 'center', gap: 24, padding: '0 29px', boxSizing: 'border-box', boxShadow: p.shadow,
|
|
483
|
-
fontFamily: SANS, color: p.ink,
|
|
484
|
-
}}>
|
|
485
|
-
<div style={{
|
|
486
|
-
width: 44, height: 44, borderRadius: 10, background: i === 1 ? p.accentSoft : p.fill, flex: 'none',
|
|
487
|
-
boxShadow: i === 1 ? 'none' : `inset 0 0 0 1px ${p.line}`, display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
488
|
-
}}>
|
|
489
|
-
<Ic name={r.icon} size={20} color={i === 1 ? p.accent : p.ink2} />
|
|
490
|
-
</div>
|
|
491
|
-
<div style={{ width: 400, flex: 'none' }}>
|
|
492
|
-
<div style={{ fontSize: 20, fontWeight: 600, letterSpacing: '-0.015em', whiteSpace: 'nowrap' }}>{r.title}</div>
|
|
493
|
-
<div style={{ marginTop: 5, fontSize: 14.5, color: p.ink3, whiteSpace: 'nowrap' }}>{r.sub}</div>
|
|
494
|
-
</div>
|
|
495
|
-
<div style={{ width: 150, flex: 'none', display: 'flex' }}>
|
|
496
|
-
<Chip p={p} dot accent={live} size={13.5}>{r.status}</Chip>
|
|
497
|
-
</div>
|
|
498
|
-
<div style={{ display: 'flex', flex: 'none', width: 90 }}>
|
|
499
|
-
{[0, 1, 2].slice(0, 1 + ((i + 1) % 3)).map((j) => (
|
|
500
|
-
<div key={j} style={{ marginLeft: j ? -5 : 0 }}><Avatar i={i * 2 + j} size={32} p={p} ring /></div>
|
|
501
|
-
))}
|
|
502
|
-
</div>
|
|
503
|
-
<svg width={sw} height={sh} style={{ display: 'block', flex: 'none', overflow: 'visible' }}>
|
|
504
|
-
<path d={spark} fill="none" stroke={live ? p.accent : p.chart2} strokeWidth={2} strokeLinecap="round" />
|
|
505
|
-
</svg>
|
|
506
|
-
<div style={{ width: 130, flex: 'none', textAlign: 'right' }}>
|
|
507
|
-
<div style={{ fontSize: 21, fontWeight: 600, letterSpacing: '-0.02em', fontVariantNumeric: 'tabular-nums' }}>{r.val}</div>
|
|
508
|
-
<div style={{ marginTop: 3, fontSize: 13, color: p.ink3 }}>{r.unit}</div>
|
|
509
|
-
</div>
|
|
510
|
-
<div style={{ width: 150, flex: 'none', display: 'flex', alignItems: 'center', gap: 8, fontSize: 14.5, color: p.ink2, whiteSpace: 'nowrap' }}>
|
|
511
|
-
<Ic name="clock" size={16} color={p.ink3} />
|
|
512
|
-
{r.due}
|
|
513
|
-
</div>
|
|
514
|
-
<div style={{ marginLeft: 'auto', width: 120, flex: 'none' }}>
|
|
515
|
-
<div style={{ display: 'flex', fontSize: 13, color: p.ink3, marginBottom: 8 }}>
|
|
516
|
-
<span>Progress</span>
|
|
517
|
-
<span style={{ marginLeft: 'auto', color: p.ink2, fontWeight: 500, fontVariantNumeric: 'tabular-nums' }}>{r.pct}%</span>
|
|
518
|
-
</div>
|
|
519
|
-
<div style={{ height: 6, borderRadius: 3, background: p.fill2, overflow: 'hidden' }}>
|
|
520
|
-
<div style={{ width: `${r.pct}%`, height: '100%', borderRadius: 3, background: live ? p.accent : p.chart2 }} />
|
|
521
|
-
</div>
|
|
522
|
-
</div>
|
|
523
|
-
</div>
|
|
524
|
-
);
|
|
525
|
-
};
|
|
526
|
-
|
|
527
|
-
// 假 dashboard 面板:A = 3×2 卡片网格,B = 列表行。1920×1080。
|
|
528
|
-
// 几何(demo 依赖,勿改):侧栏 220;顶栏 72;A 卡 524×454 @ x=256/808/1360, y=108/590;
|
|
529
|
-
// B 行 1628×171.2 @ y=108+i×191.2,行首 44px 图标块圆心 x=308。
|
|
530
|
-
export const FakeDashboard: React.FC<{ variant?: 'A' | 'B'; tone?: Tone }> = ({ variant = 'A', tone = 'light' }) => {
|
|
531
|
-
const p = P[tone];
|
|
532
|
-
const active = variant === 'A' ? 0 : 2; // 侧栏高亮项与顶栏页名一致
|
|
533
|
-
return (
|
|
534
|
-
<div style={{ width: 1920, height: 1080, background: p.canvas, display: 'flex', fontFamily: SANS }}>
|
|
535
|
-
{/* 侧栏 */}
|
|
536
|
-
<div style={{
|
|
537
|
-
width: 220, background: p.side, padding: '28px 22px', boxSizing: 'border-box', display: 'flex',
|
|
538
|
-
flexDirection: 'column', gap: 6, boxShadow: `inset -1px 0 0 ${p.sideLine}`, flex: 'none',
|
|
539
|
-
}}>
|
|
540
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 11, marginBottom: 22 }}>
|
|
541
|
-
<div style={{
|
|
542
|
-
width: 40, height: 40, borderRadius: 10, flex: 'none', display: 'grid', placeItems: 'center',
|
|
543
|
-
background: 'linear-gradient(145deg, #2a2a26 0%, #171714 100%)',
|
|
544
|
-
boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.14), 0 0 0 1px rgba(0,0,0,0.25)',
|
|
545
|
-
}}>
|
|
546
|
-
<ShotcraftMark size={26} tone="dark" />
|
|
547
|
-
</div>
|
|
548
|
-
<div style={{ minWidth: 0 }}>
|
|
549
|
-
<div style={{ fontSize: 15, fontWeight: 650, color: p.sideInk, letterSpacing: '0.01em' }}>video-shotcraft</div>
|
|
550
|
-
<div style={{ fontSize: 12, color: p.sideInk3, marginTop: 2 }}>Pro plan</div>
|
|
551
|
-
</div>
|
|
552
|
-
</div>
|
|
553
|
-
{NAV.map(([icon, label], i) => (
|
|
554
|
-
<div key={label} style={{
|
|
555
|
-
height: 34, borderRadius: 8, display: 'flex', alignItems: 'center', gap: 11, padding: '0 10px',
|
|
556
|
-
background: i === active ? p.sideFill : 'transparent', color: i === active ? p.sideInk : p.sideInk2,
|
|
557
|
-
fontSize: 14, fontWeight: i === active ? 550 : 450, boxShadow: i === active ? 'inset 0 1px 0 rgba(255,255,255,0.05)' : 'none',
|
|
558
|
-
}}>
|
|
559
|
-
<Ic name={icon} size={16} color={i === active ? p.sideInk : p.sideInk2} />
|
|
560
|
-
{label}
|
|
561
|
-
</div>
|
|
562
|
-
))}
|
|
563
|
-
<div style={{ marginTop: 26, padding: '0 10px', fontSize: 11, fontWeight: 600, letterSpacing: '0.08em', color: p.sideInk3 }}>PROJECTS</div>
|
|
564
|
-
{['Launch film', 'Feature tour', 'Release reel'].map((n, i) => (
|
|
565
|
-
<div key={n} style={{ height: 32, display: 'flex', alignItems: 'center', gap: 11, padding: '0 10px', fontSize: 14, color: p.sideInk2 }}>
|
|
566
|
-
<div style={{ width: 8, height: 8, borderRadius: 3, margin: '0 4px', background: i === 0 ? p.accent : `rgba(255,255,255,${0.42 - i * 0.12})` }} />
|
|
567
|
-
{n}
|
|
568
|
-
</div>
|
|
569
|
-
))}
|
|
570
|
-
<div style={{ marginTop: 'auto', padding: 12, borderRadius: 10, background: 'rgba(255,255,255,0.04)', boxShadow: `inset 0 0 0 1px ${p.sideLine}` }}>
|
|
571
|
-
<div style={{ display: 'flex', fontSize: 12, color: p.sideInk2 }}>
|
|
572
|
-
<span>Usage</span>
|
|
573
|
-
<span style={{ marginLeft: 'auto', color: p.sideInk, fontVariantNumeric: 'tabular-nums' }}>68%</span>
|
|
574
|
-
</div>
|
|
575
|
-
<div style={{ marginTop: 8, height: 4, borderRadius: 2, background: 'rgba(255,255,255,0.08)' }}>
|
|
576
|
-
<div style={{ width: '68%', height: '100%', borderRadius: 2, background: 'rgba(255,255,255,0.7)' }} />
|
|
577
|
-
</div>
|
|
578
|
-
</div>
|
|
579
|
-
</div>
|
|
580
|
-
<div style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
|
|
581
|
-
{/* 顶栏 */}
|
|
582
|
-
<div style={{
|
|
583
|
-
height: 72, background: p.raised, borderBottom: `1px solid ${p.line}`, display: 'flex', alignItems: 'center',
|
|
584
|
-
padding: '0 32px', gap: 20, boxSizing: 'border-box', flex: 'none',
|
|
585
|
-
}}>
|
|
586
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 15, whiteSpace: 'nowrap' }}>
|
|
587
|
-
<span style={{ color: p.ink3 }}>video-shotcraft</span>
|
|
588
|
-
<Ic name="chevron" size={14} color={p.ink3} />
|
|
589
|
-
<span style={{ color: p.ink, fontWeight: 600, letterSpacing: '-0.01em' }}>{variant === 'A' ? 'Overview' : 'Projects'}</span>
|
|
590
|
-
</div>
|
|
591
|
-
<div style={{ display: 'flex', padding: 3, borderRadius: 9, background: p.fill, boxShadow: `inset 0 0 0 1px ${p.line}`, fontSize: 13, marginLeft: 12 }}>
|
|
592
|
-
{['7d', '30d', '90d'].map((t, i) => (
|
|
593
|
-
<div key={t} style={{
|
|
594
|
-
padding: '5px 11px', borderRadius: 6, color: i === 1 ? p.ink : p.ink3, fontWeight: i === 1 ? 550 : 450,
|
|
595
|
-
background: i === 1 ? p.surface : 'transparent', boxShadow: i === 1 ? `0 1px 2px rgba(16,18,24,0.08), inset 0 0 0 1px ${p.line}` : 'none',
|
|
596
|
-
}}>{t}</div>
|
|
597
|
-
))}
|
|
598
|
-
</div>
|
|
599
|
-
<div style={{
|
|
600
|
-
marginLeft: 'auto', height: 36, width: 320, background: p.surface, border: `1px solid ${p.line2}`, borderRadius: 10,
|
|
601
|
-
boxSizing: 'border-box', display: 'flex', alignItems: 'center', gap: 9, padding: '0 8px 0 12px', flex: 'none',
|
|
602
|
-
boxShadow: tone === 'light' ? '0 1px 2px rgba(16,18,24,0.04)' : 'none',
|
|
603
|
-
}}>
|
|
604
|
-
<Ic name="search" size={15} color={p.ink3} />
|
|
605
|
-
<span style={{ fontSize: 14, color: p.ink3 }}>Search…</span>
|
|
606
|
-
<span style={{
|
|
607
|
-
marginLeft: 'auto', fontSize: 11.5, fontWeight: 500, color: p.ink3, padding: '2px 6px', borderRadius: 5,
|
|
608
|
-
boxShadow: `inset 0 0 0 1px ${p.line2}`, background: p.fill,
|
|
609
|
-
}}>⌘K</span>
|
|
610
|
-
</div>
|
|
611
|
-
<div style={{
|
|
612
|
-
width: 36, height: 36, borderRadius: 18, flex: 'none', display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
613
|
-
background: 'linear-gradient(145deg, #3a3c44, #23242a)', color: 'rgba(255,255,255,0.92)', fontSize: 13, fontWeight: 600,
|
|
614
|
-
boxShadow: `0 0 0 2px ${p.raised}, 0 0 0 3px ${p.line2}`,
|
|
615
|
-
}}>JL</div>
|
|
616
|
-
</div>
|
|
617
|
-
{variant === 'A' ? (
|
|
618
|
-
<div style={{ flex: 1, padding: 36, display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gridAutoRows: '1fr', gap: 28, boxSizing: 'border-box' }}>
|
|
619
|
-
{Array.from({ length: 6 }).map((_, i) => (
|
|
620
|
-
<Card key={i} w={524} h={454} seed={i + 1} tone={tone} style={{ width: '100%', height: '100%' }} />
|
|
621
|
-
))}
|
|
622
|
-
</div>
|
|
623
|
-
) : (
|
|
624
|
-
<div style={{ flex: 1, padding: 36, display: 'flex', flexDirection: 'column', gap: 20, boxSizing: 'border-box' }}>
|
|
625
|
-
{Array.from({ length: 5 }).map((_, i) => (
|
|
626
|
-
<ListRow key={i} i={i} p={p} />
|
|
627
|
-
))}
|
|
628
|
-
</div>
|
|
629
|
-
)}
|
|
630
|
-
</div>
|
|
631
|
-
</div>
|
|
632
|
-
);
|
|
633
|
-
};
|
|
634
|
-
|
|
635
|
-
// 标题字块:系统字体栈 + 700 字重 + 随字号的负字距(全大写放松字距),默认 88 号,占位面积与旧版相当
|
|
636
|
-
export const TitleBlock: React.FC<{ text: string; size?: number }> = ({ text, size = 88 }) => {
|
|
637
|
-
const caps = /[A-Z]/.test(text) && text === text.toUpperCase();
|
|
638
|
-
return (
|
|
639
|
-
<div style={{
|
|
640
|
-
fontFamily: SANS, fontWeight: 700, fontSize: size, color: G.ink1, lineHeight: 1.12,
|
|
641
|
-
letterSpacing: caps ? '-0.012em' : '-0.032em', fontVariantNumeric: 'tabular-nums',
|
|
642
|
-
fontKerning: 'normal',
|
|
643
|
-
}}>
|
|
644
|
-
{text}
|
|
645
|
-
</div>
|
|
646
|
-
);
|
|
647
|
-
};
|
|
1
|
+
// 标准占位素材包(孵化轮 PLAYBOOK ③)——卡片组 + 假 dashboard 面板 + 标题字块。
|
|
2
|
+
// Phase 0 质感升级后:外形几何与导出签名完全不变(Card 填满 w×h、同 padding 盒;
|
|
3
|
+
// FakeDashboard 1920×1080、侧栏 220、顶栏 72、A=3×2 网格 pad36/gap28、B=5 行 pad36/gap20;
|
|
4
|
+
// TitleBlock 默认 88 号),只把渲染从灰阶骨架条换成克制的出版级产品 UI:发丝线细边、
|
|
5
|
+
// 两层软阴影 + 顶部内高光、真实感假内容(指标数字 / sparkline / 首字母头像 / 状态 chip /
|
|
6
|
+
// 内联 SVG 图标)、系统字体栈与字重层级。中性暖灰 + 1 个安静的强调色,永远不抢运动的戏。
|
|
7
|
+
// 全部内容由 seed 确定性生成(无随机源)。可选 tone="dark" 出同布局深色版(换肤类 demo 用)。
|
|
8
|
+
// 依赖 react + ./Brand(侧栏/顶栏的 video-shotcraft 标志与名字);copy demo 时一并带上这两个文件并改 import 路径。
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import { ShotcraftMark } from './Brand';
|
|
11
|
+
|
|
12
|
+
const SANS = '-apple-system, BlinkMacSystemFont, "SF Pro Display", "Helvetica Neue", Inter, Arial, sans-serif';
|
|
13
|
+
const MONO = '"SF Mono", "JetBrains Mono", Menlo, monospace';
|
|
14
|
+
|
|
15
|
+
// 调色板:前 10 个键是历史值,demo 大量直接引用,键名与取值永远不改;
|
|
16
|
+
// 后面是 Phase 0 新增的精致 UI 令牌,per-demo 自绘元素可直接取用。
|
|
17
|
+
export const G = {
|
|
18
|
+
bg: '#ececea',
|
|
19
|
+
panel: '#f7f7f6',
|
|
20
|
+
line: '#dcdcda',
|
|
21
|
+
bar: '#c2c2c0',
|
|
22
|
+
ink: '#2f2f2f',
|
|
23
|
+
mid: '#8f8f8d',
|
|
24
|
+
card: '#ffffff',
|
|
25
|
+
border: '#d8d8d6',
|
|
26
|
+
side: '#3a3a3a',
|
|
27
|
+
sideBar: '#5a5a58',
|
|
28
|
+
// —— 新增令牌 ——
|
|
29
|
+
font: SANS, // 系统无衬线字体栈
|
|
30
|
+
mono: MONO, // 等宽字体栈
|
|
31
|
+
canvas: '#f1f1ef', // dashboard 内容区底色(比 bg 略亮)
|
|
32
|
+
ink1: '#17181c', // 主文字(带一点冷调的近黑,替代纯黑)
|
|
33
|
+
ink2: '#5d5f66', // 次级文字
|
|
34
|
+
ink3: '#9b9da3', // 三级文字 / 占位提示
|
|
35
|
+
fill: '#f3f3f1', // 浅填充(chip / 图标底 / 输入框)
|
|
36
|
+
fill2: '#e7e7e4', // 中填充(图表底色 / 未激活柱)
|
|
37
|
+
hairline: 'rgba(20,22,28,0.08)', // 1px 发丝线颜色(用法:border: `1px solid ${G.hairline}`)
|
|
38
|
+
hairlineStrong: 'rgba(20,22,28,0.13)', // 稍强的发丝线(输入框 / 分隔)
|
|
39
|
+
accent: '#5b63d3', // 唯一强调色:安静的靛蓝
|
|
40
|
+
accentSoft: 'rgba(91,99,211,0.11)', // 强调色浅底(chip / 选中态)
|
|
41
|
+
dark: '#111216', // 带色相的深色场景底(替代 #000)
|
|
42
|
+
darkSurface: '#18191e', // 深色场景里的卡片面
|
|
43
|
+
shadowSm: 'inset 0 1px 0 rgba(255,255,255,0.85), 0 1px 2px rgba(16,18,24,0.06), 0 6px 18px -6px rgba(16,18,24,0.10)',
|
|
44
|
+
shadowMd: 'inset 0 1px 0 rgba(255,255,255,0.85), 0 2px 4px rgba(16,18,24,0.06), 0 16px 40px -12px rgba(16,18,24,0.18)',
|
|
45
|
+
shadowLg: 'inset 0 1px 0 rgba(255,255,255,0.85), 0 4px 10px rgba(16,18,24,0.08), 0 36px 80px -20px rgba(16,18,24,0.30)',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
type Tone = 'light' | 'dark';
|
|
49
|
+
|
|
50
|
+
// 内部双主题令牌(light 与 G 新增键一致;dark 为同布局深色版)
|
|
51
|
+
const P = {
|
|
52
|
+
light: {
|
|
53
|
+
canvas: 'linear-gradient(180deg, #f4f4f2 0%, #eeeeeb 100%)',
|
|
54
|
+
surface: G.card, raised: '#fafaf9', line: G.hairline, line2: G.hairlineStrong,
|
|
55
|
+
ink: G.ink1, ink2: G.ink2, ink3: G.ink3, fill: G.fill, fill2: G.fill2,
|
|
56
|
+
chart: '#dcdde1', chart2: '#c4c6cc', accent: G.accent, accentSoft: G.accentSoft,
|
|
57
|
+
side: 'linear-gradient(180deg, #1c1d22 0%, #16171b 100%)', sideLine: 'rgba(255,255,255,0.06)',
|
|
58
|
+
sideInk: 'rgba(255,255,255,0.94)', sideInk2: 'rgba(255,255,255,0.56)', sideInk3: 'rgba(255,255,255,0.32)',
|
|
59
|
+
sideFill: 'rgba(255,255,255,0.075)', ring: '#ffffff',
|
|
60
|
+
shadow: G.shadowSm, avatar: ['#e4e4e1', '#d9dade', '#e9e6df', '#dfe1e8'], avatarInk: '#4a4c53',
|
|
61
|
+
},
|
|
62
|
+
dark: {
|
|
63
|
+
canvas: 'linear-gradient(180deg, #121318 0%, #0e0f13 100%)',
|
|
64
|
+
surface: G.darkSurface, raised: '#141519', line: 'rgba(255,255,255,0.07)', line2: 'rgba(255,255,255,0.12)',
|
|
65
|
+
ink: '#ededf0', ink2: '#a3a5ad', ink3: '#6c6e76', fill: 'rgba(255,255,255,0.05)', fill2: 'rgba(255,255,255,0.08)',
|
|
66
|
+
chart: 'rgba(255,255,255,0.10)', chart2: 'rgba(255,255,255,0.18)', accent: '#8088f0', accentSoft: 'rgba(128,136,240,0.16)',
|
|
67
|
+
side: 'linear-gradient(180deg, #0c0d10 0%, #0a0b0e 100%)', sideLine: 'rgba(255,255,255,0.06)',
|
|
68
|
+
sideInk: 'rgba(255,255,255,0.94)', sideInk2: 'rgba(255,255,255,0.52)', sideInk3: 'rgba(255,255,255,0.28)',
|
|
69
|
+
sideFill: 'rgba(255,255,255,0.07)', ring: G.darkSurface,
|
|
70
|
+
shadow: 'inset 0 1px 0 rgba(255,255,255,0.05), 0 1px 2px rgba(0,0,0,0.35), 0 8px 24px -8px rgba(0,0,0,0.55)',
|
|
71
|
+
avatar: ['#2b2c33', '#33343b', '#2a2d38', '#30302f'], avatarInk: '#c9cad0',
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
type Pal = typeof P.light;
|
|
75
|
+
|
|
76
|
+
// —— 确定性小工具 ——
|
|
77
|
+
const hash = (n: number) => {
|
|
78
|
+
const x = Math.sin(n * 127.1 + 311.7) * 43758.5453;
|
|
79
|
+
return x - Math.floor(x);
|
|
80
|
+
};
|
|
81
|
+
const pick = <T,>(arr: T[], i: number) => arr[((i % arr.length) + arr.length) % arr.length];
|
|
82
|
+
const fmt = (n: number) => Math.round(n).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
83
|
+
// 平滑的伪数据序列:两组正弦 + 趋势 + 微噪,归一化到 [0,1]
|
|
84
|
+
const series = (seed: number, n: number, trend = 0.35) => {
|
|
85
|
+
const raw = Array.from({ length: n }, (_, i) =>
|
|
86
|
+
trend * (i / (n - 1)) +
|
|
87
|
+
0.15 * Math.sin(i * 0.52 + seed * 1.7) +
|
|
88
|
+
0.06 * Math.sin(i * 1.35 + seed * 0.6) +
|
|
89
|
+
0.04 * (hash(seed * 31 + i) - 0.5),
|
|
90
|
+
);
|
|
91
|
+
const lo = Math.min(...raw);
|
|
92
|
+
const hi = Math.max(...raw);
|
|
93
|
+
return raw.map((v) => (v - lo) / (hi - lo || 1));
|
|
94
|
+
};
|
|
95
|
+
// Catmull-Rom → 三次贝塞尔,折线变顺滑曲线
|
|
96
|
+
const smooth = (pts: [number, number][]) => {
|
|
97
|
+
let d = `M${pts[0][0].toFixed(1)},${pts[0][1].toFixed(1)}`;
|
|
98
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
99
|
+
const p0 = pts[Math.max(0, i - 1)];
|
|
100
|
+
const p1 = pts[i];
|
|
101
|
+
const p2 = pts[i + 1];
|
|
102
|
+
const p3 = pts[Math.min(pts.length - 1, i + 2)];
|
|
103
|
+
const c1x = p1[0] + (p2[0] - p0[0]) / 6;
|
|
104
|
+
const c1y = p1[1] + (p2[1] - p0[1]) / 6;
|
|
105
|
+
const c2x = p2[0] - (p3[0] - p1[0]) / 6;
|
|
106
|
+
const c2y = p2[1] - (p3[1] - p1[1]) / 6;
|
|
107
|
+
d += ` C${c1x.toFixed(1)},${c1y.toFixed(1)} ${c2x.toFixed(1)},${c2y.toFixed(1)} ${p2[0].toFixed(1)},${p2[1].toFixed(1)}`;
|
|
108
|
+
}
|
|
109
|
+
return d;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// —— 16×16 线性图标(1.5 描边,圆头圆角)——
|
|
113
|
+
const ICONS: Record<string, string[]> = {
|
|
114
|
+
home: ['M2.75 7.25 8 3l5.25 4.25', 'M4.25 6.25v6.5h7.5v-6.5'],
|
|
115
|
+
chart: ['M3 13V9', 'M6.5 13V4.5', 'M10 13V7', 'M13.5 13V3'],
|
|
116
|
+
folder: ['M2.5 4.75c0-.7.55-1.25 1.25-1.25h2.6l1.5 1.5h4.4c.7 0 1.25.55 1.25 1.25v5.5c0 .7-.55 1.25-1.25 1.25h-8.5c-.7 0-1.25-.55-1.25-1.25z'],
|
|
117
|
+
users: ['M6 7.25a2.25 2.25 0 1 0 0-4.5 2.25 2.25 0 0 0 0 4.5z', 'M2 13c.5-2.1 2-3.25 4-3.25S9.5 10.9 10 13', 'M10.5 2.9a2.25 2.25 0 0 1 0 4.2', 'M12 9.9c1 .5 1.7 1.5 2 3.1'],
|
|
118
|
+
file: ['M4.25 2.5h4.5l3 3v7.25c0 .4-.35.75-.75.75h-6.75a.75.75 0 0 1-.75-.75v-9.5c0-.4.35-.75.75-.75z', 'M8.75 2.5v3h3', 'M5.75 9h4.5', 'M5.75 11.25h3'],
|
|
119
|
+
card: ['M3.75 3.75h8.5c.7 0 1.25.55 1.25 1.25v6c0 .7-.55 1.25-1.25 1.25h-8.5c-.7 0-1.25-.55-1.25-1.25v-6c0-.7.55-1.25 1.25-1.25z', 'M2.5 6.75h11', 'M4.75 9.5h2'],
|
|
120
|
+
sliders: ['M3 4.5h5.5', 'M11.5 4.5H13', 'M3 11.5h1.5', 'M7.5 11.5H13', 'M10 3v3', 'M6 10v3'],
|
|
121
|
+
search: ['M7 11.5a4.5 4.5 0 1 0 0-9 4.5 4.5 0 0 0 0 9z', 'm10.5 10.5 3 3'],
|
|
122
|
+
bolt: ['M8.75 2 3.75 9h4l-.5 5 5-7h-4z'],
|
|
123
|
+
globe: ['M8 13.5a5.5 5.5 0 1 0 0-11 5.5 5.5 0 0 0 0 11z', 'M2.5 8h11', 'M8 2.5c1.6 1.6 2.25 3.5 2.25 5.5S9.6 11.9 8 13.5C6.4 11.9 5.75 10 5.75 8S6.4 4.1 8 2.5z'],
|
|
124
|
+
layers: ['M8 2.5 13.5 5.5 8 8.5 2.5 5.5z', 'm2.5 8.25 5.5 3 5.5-3', 'm2.5 10.75 5.5 3 5.5-3'],
|
|
125
|
+
pulse: ['M2 8.5h2.5l1.5-4 3 8 1.5-4H14'],
|
|
126
|
+
target: ['M8 13.5a5.5 5.5 0 1 0 0-11 5.5 5.5 0 0 0 0 11z', 'M8 10.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z'],
|
|
127
|
+
grid: ['M3 3h4v4H3z', 'M9 3h4v4H9z', 'M3 9h4v4H3z', 'M9 9h4v4H9z'],
|
|
128
|
+
inbox: ['M2.5 9h3l1 1.75h3l1-1.75h3', 'M4.25 3.5h7.5l1.75 5.5v3.25c0 .4-.35.75-.75.75h-9.5a.75.75 0 0 1-.75-.75V9z'],
|
|
129
|
+
arrowUR: ['M5 11 11 5', 'M6 5h5v5'],
|
|
130
|
+
arrowR: ['M3.5 8h9', 'M9 4.5 12.5 8 9 11.5'],
|
|
131
|
+
chevron: ['m6 4 4 4-4 4'],
|
|
132
|
+
clock: ['M8 13.5a5.5 5.5 0 1 0 0-11 5.5 5.5 0 0 0 0 11z', 'M8 5v3.25l2.25 1.5'],
|
|
133
|
+
bell: ['M4.5 6.5a3.5 3.5 0 0 1 7 0c0 3 1.25 4.25 1.25 4.25h-9.5S4.5 9.5 4.5 6.5z', 'M6.75 13a1.4 1.4 0 0 0 2.5 0'],
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const Ic: React.FC<{ name: string; size?: number; color: string; sw?: number }> = ({ name, size = 16, color, sw = 1.5 }) => (
|
|
137
|
+
<svg width={size} height={size} viewBox="0 0 16 16" fill="none" stroke={color} strokeWidth={sw}
|
|
138
|
+
strokeLinecap="round" strokeLinejoin="round" style={{ display: 'block', flex: 'none' }}>
|
|
139
|
+
{(ICONS[name] ?? []).map((d, i) => <path key={i} d={d} />)}
|
|
140
|
+
</svg>
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const More: React.FC<{ color: string; size?: number }> = ({ color, size = 16 }) => (
|
|
144
|
+
<svg width={size} height={size} viewBox="0 0 16 16" style={{ display: 'block', flex: 'none' }}>
|
|
145
|
+
{[3.5, 8, 12.5].map((x) => <circle key={x} cx={x} cy={8} r={1.25} fill={color} />)}
|
|
146
|
+
</svg>
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const INITIALS = ['AK', 'MR', 'JL', 'SO', 'TN', 'EV', 'DP', 'CW'];
|
|
150
|
+
const Avatar: React.FC<{ i: number; size: number; p: Pal; ring?: boolean }> = ({ i, size, p, ring }) => (
|
|
151
|
+
<div style={{
|
|
152
|
+
width: size, height: size, borderRadius: size / 2, flex: 'none', boxSizing: 'border-box',
|
|
153
|
+
background: pick(p.avatar, i), color: p.avatarInk, display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
154
|
+
fontSize: size * 0.36, fontWeight: 600, letterSpacing: '0.01em',
|
|
155
|
+
boxShadow: ring ? `0 0 0 2px ${p.ring}` : `inset 0 0 0 1px ${p.line}`,
|
|
156
|
+
}}>
|
|
157
|
+
{pick(INITIALS, i)}
|
|
158
|
+
</div>
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
const Chip: React.FC<{ p: Pal; children: React.ReactNode; accent?: boolean; dot?: boolean; size?: number }> = ({ p, children, accent, dot, size = 11.5 }) => (
|
|
162
|
+
<div style={{
|
|
163
|
+
display: 'flex', alignItems: 'center', gap: size * 0.45, flex: 'none', whiteSpace: 'nowrap',
|
|
164
|
+
padding: `${size * 0.28}px ${size * 0.62}px`, borderRadius: size * 0.55, fontSize: size, fontWeight: 500,
|
|
165
|
+
lineHeight: 1.2, color: accent ? p.accent : p.ink2, background: accent ? p.accentSoft : p.fill,
|
|
166
|
+
boxShadow: accent ? 'none' : `inset 0 0 0 1px ${p.line}`,
|
|
167
|
+
}}>
|
|
168
|
+
{dot && <div style={{ width: size * 0.5, height: size * 0.5, borderRadius: size, background: accent ? p.accent : p.ink3 }} />}
|
|
169
|
+
{children}
|
|
170
|
+
</div>
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
// —— 卡片内容表(seed 决定类型与文案;全部虚构、通用)——
|
|
174
|
+
type Kind = 'area' | 'list' | 'bars' | 'progress' | 'heat';
|
|
175
|
+
const KINDS: Kind[] = ['area', 'list', 'bars', 'progress', 'heat'];
|
|
176
|
+
const COPY: Record<Kind, { title: string; icon: string; value: string; delta?: string; caption: string; chip: string }[]> = {
|
|
177
|
+
area: [
|
|
178
|
+
{ title: 'Active users', icon: 'users', value: '24,812', delta: '+12.4%', caption: 'vs. previous 7 days', chip: '7d' },
|
|
179
|
+
{ title: 'Sessions', icon: 'pulse', value: '182,340', delta: '+8.1%', caption: 'vs. previous 30 days', chip: '30d' },
|
|
180
|
+
{ title: 'API requests', icon: 'bolt', value: '1.20M', delta: '+3.6%', caption: 'p95 latency 148 ms', chip: '24h' },
|
|
181
|
+
],
|
|
182
|
+
list: [
|
|
183
|
+
{ title: 'Top pages', icon: 'globe', value: '', caption: '', chip: 'Live' },
|
|
184
|
+
{ title: 'Recent activity', icon: 'inbox', value: '', caption: '', chip: 'Today' },
|
|
185
|
+
],
|
|
186
|
+
bars: [
|
|
187
|
+
{ title: 'Revenue', icon: 'chart', value: '$48.2k', delta: '+6.3%', caption: 'Net volume this month', chip: 'Monthly' },
|
|
188
|
+
{ title: 'Orders', icon: 'card', value: '3,912', delta: '+4.8%', caption: 'Across all channels', chip: 'Weekly' },
|
|
189
|
+
],
|
|
190
|
+
progress: [
|
|
191
|
+
{ title: 'Quarterly goals', icon: 'target', value: '68%', caption: 'of Q3 target reached', chip: 'Q3' },
|
|
192
|
+
{ title: 'Sprint 24', icon: 'layers', value: '74%', caption: '31 of 42 issues closed', chip: '6d left' },
|
|
193
|
+
],
|
|
194
|
+
heat: [
|
|
195
|
+
{ title: 'Deploys', icon: 'grid', value: '1,284', caption: 'in the last 12 weeks', chip: '12w' },
|
|
196
|
+
{ title: 'Activity', icon: 'grid', value: '412', caption: 'events this quarter', chip: 'Q3' },
|
|
197
|
+
],
|
|
198
|
+
};
|
|
199
|
+
const PAGES = ['/pricing', '/docs/getting-started', '/changelog', '/blog/release-notes', '/integrations', '/careers'];
|
|
200
|
+
const EVENTS = ['merged #482 into main', 'commented on Roadmap', 'deployed v2.14.0', 'opened Billing v2', 'closed 3 issues', 'invited 2 members'];
|
|
201
|
+
const GOALS = ['Design system', 'Mobile app', 'Billing v2', 'Docs refresh', 'Search'];
|
|
202
|
+
|
|
203
|
+
// 卡片设计基准:网格卡 524×454 的内容区(减去 border+padding 20)
|
|
204
|
+
const BASE_W = 484;
|
|
205
|
+
const BASE_H = 414;
|
|
206
|
+
const HEADER = 28;
|
|
207
|
+
const GAP = 16;
|
|
208
|
+
const METRIC = 64;
|
|
209
|
+
const FOOTER = 54;
|
|
210
|
+
|
|
211
|
+
const Body: React.FC<{ kind: Kind; w: number; h: number; seed: number; p: Pal; id: string }> = ({ kind, w, h, seed, p, id }) => {
|
|
212
|
+
if (kind === 'area') {
|
|
213
|
+
const n = 26;
|
|
214
|
+
const v = series(seed, n, 0.42);
|
|
215
|
+
const prev = series(seed + 11, n, 0.1);
|
|
216
|
+
const yOf = (x: number) => 8 + (1 - x) * (h - 16);
|
|
217
|
+
const pts = v.map((x, i) => [(i / (n - 1)) * (w - 6), yOf(x * 0.82 + 0.1)] as [number, number]);
|
|
218
|
+
const ppts = prev.map((x, i) => [(i / (n - 1)) * (w - 6), yOf(x * 0.5 + 0.12)] as [number, number]);
|
|
219
|
+
const line = smooth(pts);
|
|
220
|
+
const last = pts[n - 1];
|
|
221
|
+
return (
|
|
222
|
+
<svg width={w} height={h} style={{ display: 'block', overflow: 'visible' }}>
|
|
223
|
+
<defs>
|
|
224
|
+
<linearGradient id={id} x1="0" y1="0" x2="0" y2="1">
|
|
225
|
+
<stop offset="0" stopColor={p.accent} stopOpacity={0.2} />
|
|
226
|
+
<stop offset="1" stopColor={p.accent} stopOpacity={0} />
|
|
227
|
+
</linearGradient>
|
|
228
|
+
</defs>
|
|
229
|
+
{[0.12, 0.5, 0.88].map((t) => (
|
|
230
|
+
<line key={t} x1={0} x2={w} y1={h * t} y2={h * t} stroke={p.line} strokeWidth={1} strokeDasharray="2 5" />
|
|
231
|
+
))}
|
|
232
|
+
<path d={smooth(ppts)} fill="none" stroke={p.chart2} strokeWidth={1.5} strokeDasharray="3 4" />
|
|
233
|
+
<path d={`${line} L${last[0].toFixed(1)},${h} L0,${h} Z`} fill={`url(#${id})`} />
|
|
234
|
+
<path d={line} fill="none" stroke={p.accent} strokeWidth={2.25} strokeLinecap="round" />
|
|
235
|
+
<circle cx={last[0]} cy={last[1]} r={9} fill={p.accent} opacity={0.14} />
|
|
236
|
+
<circle cx={last[0]} cy={last[1]} r={4} fill={p.surface} stroke={p.accent} strokeWidth={2} />
|
|
237
|
+
</svg>
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
if (kind === 'bars') {
|
|
241
|
+
const n = Math.max(8, Math.min(24, Math.round(w / 34)));
|
|
242
|
+
const v = series(seed + 3, n, 0.3);
|
|
243
|
+
const labelH = h >= 96 ? 20 : 0;
|
|
244
|
+
const ch = h - labelH;
|
|
245
|
+
const slot = w / n;
|
|
246
|
+
const bw = slot * 0.56;
|
|
247
|
+
const peak = v.indexOf(Math.max(...v));
|
|
248
|
+
const step = Math.ceil(n / 6); // 每 step 根柱一个月份刻度
|
|
249
|
+
const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
250
|
+
return (
|
|
251
|
+
<svg width={w} height={h} style={{ display: 'block' }}>
|
|
252
|
+
<line x1={0} x2={w} y1={ch - 0.5} y2={ch - 0.5} stroke={p.line} strokeWidth={1} />
|
|
253
|
+
{v.map((x, i) => {
|
|
254
|
+
const bh = Math.max(4, (0.16 + x * 0.84) * (ch - 6));
|
|
255
|
+
return (
|
|
256
|
+
<rect key={i} x={i * slot + (slot - bw) / 2} y={ch - bh - 1} width={bw} height={bh} rx={Math.min(4, bw / 3)}
|
|
257
|
+
fill={i === peak ? p.accent : p.chart} />
|
|
258
|
+
);
|
|
259
|
+
})}
|
|
260
|
+
{labelH > 0 && v.map((_, i) => (i % step === 0 ? (
|
|
261
|
+
<text key={i} x={i * slot + slot / 2} y={h - 4} textAnchor="middle" fontSize={11} fill={p.ink3} fontFamily={SANS}>
|
|
262
|
+
{MONTHS[(i / step + 7) % 12]}
|
|
263
|
+
</text>
|
|
264
|
+
) : null))}
|
|
265
|
+
</svg>
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
if (kind === 'list') {
|
|
269
|
+
const rowH = 42;
|
|
270
|
+
const rows = Math.max(1, Math.min(6, Math.floor(h / rowH)));
|
|
271
|
+
const pages = seed % 2 === 0;
|
|
272
|
+
return (
|
|
273
|
+
<div style={{ width: w, height: h, display: 'flex', flexDirection: 'column' }}>
|
|
274
|
+
{Array.from({ length: rows }).map((_, i) => {
|
|
275
|
+
const val = Math.round(9000 * Math.pow(0.62, i) * (0.8 + hash(seed * 7 + i) * 0.4));
|
|
276
|
+
const share = Math.pow(0.66, i);
|
|
277
|
+
return (
|
|
278
|
+
<div key={i} style={{
|
|
279
|
+
height: rowH, flex: 'none', display: 'flex', alignItems: 'center', gap: 10, position: 'relative',
|
|
280
|
+
borderTop: i === 0 ? 'none' : `1px solid ${p.line}`, boxSizing: 'border-box',
|
|
281
|
+
}}>
|
|
282
|
+
{pages ? (
|
|
283
|
+
<>
|
|
284
|
+
<div style={{ position: 'absolute', left: 0, top: 8, bottom: 8, width: `${share * 100}%`, borderRadius: 6, background: i === 0 ? p.accentSoft : p.fill }} />
|
|
285
|
+
<div style={{ position: 'relative', paddingLeft: 10, fontSize: 13.5, color: p.ink, fontWeight: 500, fontFamily: MONO, letterSpacing: '-0.01em', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', minWidth: 0 }}>
|
|
286
|
+
{pick(PAGES, i + seed)}
|
|
287
|
+
</div>
|
|
288
|
+
</>
|
|
289
|
+
) : (
|
|
290
|
+
<>
|
|
291
|
+
<Avatar i={i + seed} size={26} p={p} />
|
|
292
|
+
<div style={{ fontSize: 13.5, color: p.ink2, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', minWidth: 0 }}>
|
|
293
|
+
<span style={{ color: p.ink, fontWeight: 600 }}>{pick(INITIALS, i + seed)}</span>{' '}{pick(EVENTS, i + seed)}
|
|
294
|
+
</div>
|
|
295
|
+
</>
|
|
296
|
+
)}
|
|
297
|
+
<div style={{ position: 'relative', marginLeft: 'auto', paddingRight: 4, fontSize: 13, color: p.ink3, fontVariantNumeric: 'tabular-nums', whiteSpace: 'nowrap' }}>
|
|
298
|
+
{pages ? fmt(val) : `${2 + i * 7 + (seed % 5)}m`}
|
|
299
|
+
</div>
|
|
300
|
+
</div>
|
|
301
|
+
);
|
|
302
|
+
})}
|
|
303
|
+
</div>
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
if (kind === 'progress') {
|
|
307
|
+
const rowH = 46;
|
|
308
|
+
const rows = Math.max(1, Math.min(5, Math.floor(h / rowH)));
|
|
309
|
+
return (
|
|
310
|
+
<div style={{ width: w, height: h, display: 'flex', flexDirection: 'column', justifyContent: 'flex-start' }}>
|
|
311
|
+
{Array.from({ length: rows }).map((_, i) => {
|
|
312
|
+
const pct = Math.round(92 - i * 13 - hash(seed * 3 + i) * 12);
|
|
313
|
+
return (
|
|
314
|
+
<div key={i} style={{ height: rowH, flex: 'none', display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 8 }}>
|
|
315
|
+
<div style={{ display: 'flex', fontSize: 13, color: p.ink2 }}>
|
|
316
|
+
<span style={{ color: p.ink, fontWeight: 500 }}>{pick(GOALS, i + seed)}</span>
|
|
317
|
+
<span style={{ marginLeft: 'auto', fontVariantNumeric: 'tabular-nums', color: p.ink3 }}>{pct}%</span>
|
|
318
|
+
</div>
|
|
319
|
+
<div style={{ height: 6, borderRadius: 3, background: p.fill2, overflow: 'hidden' }}>
|
|
320
|
+
<div style={{ width: `${pct}%`, height: '100%', borderRadius: 3, background: i === 0 ? p.accent : p.chart2 }} />
|
|
321
|
+
</div>
|
|
322
|
+
</div>
|
|
323
|
+
);
|
|
324
|
+
})}
|
|
325
|
+
</div>
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
// heat:活动热力格(7 行 × N 列)
|
|
329
|
+
const gap = 5;
|
|
330
|
+
const cell = Math.max(6, Math.min(18, Math.floor((h - gap * 6) / 7)));
|
|
331
|
+
const cols = Math.max(4, Math.floor((w + gap) / (cell + gap)));
|
|
332
|
+
// 克制配色:低档用中性灰,只有高档才上强调色
|
|
333
|
+
const fills = [p.fill2, p.chart, p.accent, p.accent, p.accent];
|
|
334
|
+
const alphas = [1, 1, 0.32, 0.58, 0.9];
|
|
335
|
+
return (
|
|
336
|
+
<svg width={w} height={h} style={{ display: 'block' }}>
|
|
337
|
+
{Array.from({ length: cols * 7 }).map((_, k) => {
|
|
338
|
+
const c = Math.floor(k / 7);
|
|
339
|
+
const r = k % 7;
|
|
340
|
+
const lvl = Math.min(4, Math.floor(Math.pow(hash(seed * 13 + k), 1.7) * 3.3 + (c / cols) * 1.5));
|
|
341
|
+
return (
|
|
342
|
+
<rect key={k} x={c * (cell + gap)} y={r * (cell + gap)} width={cell} height={cell} rx={Math.min(4, cell / 4)}
|
|
343
|
+
fill={fills[lvl]} fillOpacity={alphas[lvl]} />
|
|
344
|
+
);
|
|
345
|
+
})}
|
|
346
|
+
</svg>
|
|
347
|
+
);
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
// 卡片内容(设计坐标 Wd×Hd,外层用 CSS zoom 放到实际尺寸——布局期放大,文字按目标尺寸栅格化)
|
|
351
|
+
const CardContent: React.FC<{ Wd: number; Hd: number; seed: number; p: Pal; tone: Tone }> = ({ Wd, Hd, seed, p, tone }) => {
|
|
352
|
+
const kind = pick(KINDS, seed - 1);
|
|
353
|
+
const c = pick(COPY[kind], Math.floor((seed - 1) / KINDS.length));
|
|
354
|
+
const hasMetric = kind !== 'list';
|
|
355
|
+
const wide = Wd > Hd * 2.4;
|
|
356
|
+
const showFooter = !wide && Hd >= 340;
|
|
357
|
+
const id = `fx-area-${tone}-${seed}`;
|
|
358
|
+
const stackH = HEADER + GAP + (hasMetric && !wide ? METRIC + GAP : 0) + (showFooter ? FOOTER : 0);
|
|
359
|
+
const bodyH = wide ? Hd - HEADER - GAP : Hd - stackH;
|
|
360
|
+
const metricW = wide && hasMetric ? Math.min(260, Wd * 0.36) : 0;
|
|
361
|
+
const bodyW = wide && hasMetric ? Wd - metricW - 28 : Wd;
|
|
362
|
+
const metric = hasMetric && (
|
|
363
|
+
<div style={{ flex: 'none', width: metricW || undefined }}>
|
|
364
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 10, height: 40 }}>
|
|
365
|
+
<div style={{ fontSize: 40, fontWeight: 650, color: p.ink, letterSpacing: '-0.035em', lineHeight: 1, fontVariantNumeric: 'tabular-nums' }}>
|
|
366
|
+
{c.value}
|
|
367
|
+
</div>
|
|
368
|
+
{c.delta && (
|
|
369
|
+
<Chip p={p} accent size={12}>
|
|
370
|
+
<Ic name="arrowUR" size={11} color={p.accent} sw={1.8} />
|
|
371
|
+
{c.delta}
|
|
372
|
+
</Chip>
|
|
373
|
+
)}
|
|
374
|
+
</div>
|
|
375
|
+
<div style={{ marginTop: 9, fontSize: 13, color: p.ink3, lineHeight: '15px', whiteSpace: 'nowrap' }}>{c.caption}</div>
|
|
376
|
+
</div>
|
|
377
|
+
);
|
|
378
|
+
return (
|
|
379
|
+
<div style={{ width: Wd, height: Hd, display: 'flex', flexDirection: 'column', fontFamily: SANS, color: p.ink }}>
|
|
380
|
+
<div style={{ height: HEADER, flex: 'none', display: 'flex', alignItems: 'center', gap: 10 }}>
|
|
381
|
+
<div style={{
|
|
382
|
+
width: 28, height: 28, borderRadius: 8, background: p.fill, boxShadow: `inset 0 0 0 1px ${p.line}`,
|
|
383
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 'none',
|
|
384
|
+
}}>
|
|
385
|
+
<Ic name={c.icon} size={15} color={p.ink2} />
|
|
386
|
+
</div>
|
|
387
|
+
<div style={{ fontSize: 15, fontWeight: 600, letterSpacing: '-0.01em', whiteSpace: 'nowrap' }}>{c.title}</div>
|
|
388
|
+
<div style={{ marginLeft: 'auto' }} />
|
|
389
|
+
<Chip p={p} dot={kind === 'list'} accent={kind === 'list'}>{c.chip}</Chip>
|
|
390
|
+
<More color={p.ink3} />
|
|
391
|
+
</div>
|
|
392
|
+
<div style={{ height: GAP, flex: 'none' }} />
|
|
393
|
+
{wide ? (
|
|
394
|
+
<div style={{ display: 'flex', gap: 28, alignItems: 'flex-start', height: bodyH }}>
|
|
395
|
+
{metric}
|
|
396
|
+
{bodyH >= 36 && <Body kind={kind} w={bodyW} h={bodyH} seed={seed} p={p} id={id} />}
|
|
397
|
+
</div>
|
|
398
|
+
) : (
|
|
399
|
+
<>
|
|
400
|
+
{metric}
|
|
401
|
+
{hasMetric && <div style={{ height: GAP, flex: 'none' }} />}
|
|
402
|
+
{bodyH >= 36 && <Body kind={kind} w={Wd} h={bodyH} seed={seed} p={p} id={id} />}
|
|
403
|
+
</>
|
|
404
|
+
)}
|
|
405
|
+
{showFooter && (
|
|
406
|
+
<div style={{
|
|
407
|
+
marginTop: 'auto', height: FOOTER - 14, flex: 'none', borderTop: `1px solid ${p.line}`,
|
|
408
|
+
display: 'flex', alignItems: 'flex-end', gap: 10, boxSizing: 'border-box',
|
|
409
|
+
}}>
|
|
410
|
+
<div style={{ display: 'flex' }}>
|
|
411
|
+
{[0, 1, 2].map((j) => (
|
|
412
|
+
<div key={j} style={{ marginLeft: j ? -4 : 0 }}><Avatar i={seed + j * 3} size={24} p={p} ring /></div>
|
|
413
|
+
))}
|
|
414
|
+
</div>
|
|
415
|
+
<div style={{ fontSize: 12.5, color: p.ink3, lineHeight: '24px', whiteSpace: 'nowrap' }}>Updated {2 + (seed * 7) % 50}m ago</div>
|
|
416
|
+
<div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 5, fontSize: 12.5, fontWeight: 500, color: p.ink2, lineHeight: '24px' }}>
|
|
417
|
+
View <Ic name="arrowR" size={13} color={p.ink2} />
|
|
418
|
+
</div>
|
|
419
|
+
</div>
|
|
420
|
+
)}
|
|
421
|
+
</div>
|
|
422
|
+
);
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
export const Card: React.FC<{
|
|
426
|
+
w: number; h: number; seed?: number; style?: React.CSSProperties; tone?: Tone;
|
|
427
|
+
}> = ({ w, h, seed = 0, style, tone = 'light' }) => {
|
|
428
|
+
const p = P[tone];
|
|
429
|
+
// w/h 为 0(父级用 style 100% 撑满)时按 dashboard 网格卡 524×454 排内容
|
|
430
|
+
const W = w > 0 ? w : 524;
|
|
431
|
+
const H = h > 0 ? h : 454;
|
|
432
|
+
const pad = typeof style?.padding === 'number' ? style.padding : 19;
|
|
433
|
+
const innerW0 = Math.max(40, W - 2 * (pad + 1));
|
|
434
|
+
const innerH0 = Math.max(40, H - 2 * (pad + 1));
|
|
435
|
+
// 内容缩放系数:按面积等比(几何平均),小卡保底 0.62、大卡封顶 2.4;大卡额外内缩一点留白
|
|
436
|
+
const kOf = (iw: number, ih: number) =>
|
|
437
|
+
Math.min(Math.max(Math.sqrt((iw / BASE_W) * (ih / BASE_H)), 0.62), 2.4, iw / 300, ih / 110);
|
|
438
|
+
const extra = Math.max(0, kOf(innerW0, innerH0) - 1) * 12;
|
|
439
|
+
const innerW = innerW0 - 2 * extra;
|
|
440
|
+
const innerH = innerH0 - 2 * extra;
|
|
441
|
+
const k = kOf(innerW, innerH);
|
|
442
|
+
return (
|
|
443
|
+
<div style={{
|
|
444
|
+
// 外形与旧版一致:w×h、圆角 14、border+padding 合计 20 的内容内缩(2px 灰边 → 1px 发丝线 + 19 padding)
|
|
445
|
+
width: w, height: h, background: p.surface, border: `1px solid ${p.line}`,
|
|
446
|
+
borderRadius: 14, padding: 19, boxSizing: 'border-box',
|
|
447
|
+
display: 'flex', flexDirection: 'column', gap: 10, overflow: 'hidden',
|
|
448
|
+
boxShadow: p.shadow, ...style,
|
|
449
|
+
}}>
|
|
450
|
+
<div style={{ flex: 'none', margin: extra }}>
|
|
451
|
+
<div style={{ width: innerW / k, height: innerH / k, zoom: k }}>
|
|
452
|
+
<CardContent Wd={innerW / k} Hd={innerH / k} seed={seed} p={p} tone={tone} />
|
|
453
|
+
</div>
|
|
454
|
+
</div>
|
|
455
|
+
</div>
|
|
456
|
+
);
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
const NAV = [
|
|
460
|
+
['grid', 'Overview'], ['chart', 'Analytics'], ['folder', 'Projects'], ['users', 'Members'],
|
|
461
|
+
['file', 'Reports'], ['card', 'Billing'], ['sliders', 'Settings'],
|
|
462
|
+
];
|
|
463
|
+
const ROWS = [
|
|
464
|
+
{ icon: 'layers', title: 'Onboarding flow redesign', sub: 'Design · Updated 2h ago', status: 'In review', val: '12,480', unit: 'views', pct: 72, due: 'Due Oct 18' },
|
|
465
|
+
{ icon: 'bolt', title: 'Edge cache rollout', sub: 'Platform · Updated 5h ago', status: 'Live', val: '1.2M', unit: 'requests', pct: 100, due: 'Shipped Sep 30' },
|
|
466
|
+
{ icon: 'card', title: 'Billing v2 migration', sub: 'Payments · Updated yesterday', status: 'In progress', val: '3,912', unit: 'accounts', pct: 58, due: 'Due Nov 2' },
|
|
467
|
+
{ icon: 'globe', title: 'Docs search relaunch', sub: 'Content · Updated 2d ago', status: 'Draft', val: '846', unit: 'queries', pct: 31, due: 'Due Nov 20' },
|
|
468
|
+
{ icon: 'pulse', title: 'Realtime alerts', sub: 'Infra · Updated 3d ago', status: 'Planned', val: '214', unit: 'rules', pct: 12, due: 'Due Dec 8' },
|
|
469
|
+
];
|
|
470
|
+
|
|
471
|
+
// 列表行(variant B):外形同旧版(flex:1、圆角 14、gap 24、内容左缘 28+2)
|
|
472
|
+
const ListRow: React.FC<{ i: number; p: Pal }> = ({ i, p }) => {
|
|
473
|
+
const r = ROWS[i % ROWS.length];
|
|
474
|
+
const live = r.status === 'Live';
|
|
475
|
+
const v = series(i * 5 + 2, 18, 0.3);
|
|
476
|
+
const sw = 200;
|
|
477
|
+
const sh = 40;
|
|
478
|
+
const spark = smooth(v.map((x, j) => [(j / 17) * sw, 4 + (1 - x) * (sh - 8)] as [number, number]));
|
|
479
|
+
return (
|
|
480
|
+
<div style={{
|
|
481
|
+
flex: 1, background: p.surface, border: `1px solid ${p.line}`, borderRadius: 14, display: 'flex',
|
|
482
|
+
alignItems: 'center', gap: 24, padding: '0 29px', boxSizing: 'border-box', boxShadow: p.shadow,
|
|
483
|
+
fontFamily: SANS, color: p.ink,
|
|
484
|
+
}}>
|
|
485
|
+
<div style={{
|
|
486
|
+
width: 44, height: 44, borderRadius: 10, background: i === 1 ? p.accentSoft : p.fill, flex: 'none',
|
|
487
|
+
boxShadow: i === 1 ? 'none' : `inset 0 0 0 1px ${p.line}`, display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
488
|
+
}}>
|
|
489
|
+
<Ic name={r.icon} size={20} color={i === 1 ? p.accent : p.ink2} />
|
|
490
|
+
</div>
|
|
491
|
+
<div style={{ width: 400, flex: 'none' }}>
|
|
492
|
+
<div style={{ fontSize: 20, fontWeight: 600, letterSpacing: '-0.015em', whiteSpace: 'nowrap' }}>{r.title}</div>
|
|
493
|
+
<div style={{ marginTop: 5, fontSize: 14.5, color: p.ink3, whiteSpace: 'nowrap' }}>{r.sub}</div>
|
|
494
|
+
</div>
|
|
495
|
+
<div style={{ width: 150, flex: 'none', display: 'flex' }}>
|
|
496
|
+
<Chip p={p} dot accent={live} size={13.5}>{r.status}</Chip>
|
|
497
|
+
</div>
|
|
498
|
+
<div style={{ display: 'flex', flex: 'none', width: 90 }}>
|
|
499
|
+
{[0, 1, 2].slice(0, 1 + ((i + 1) % 3)).map((j) => (
|
|
500
|
+
<div key={j} style={{ marginLeft: j ? -5 : 0 }}><Avatar i={i * 2 + j} size={32} p={p} ring /></div>
|
|
501
|
+
))}
|
|
502
|
+
</div>
|
|
503
|
+
<svg width={sw} height={sh} style={{ display: 'block', flex: 'none', overflow: 'visible' }}>
|
|
504
|
+
<path d={spark} fill="none" stroke={live ? p.accent : p.chart2} strokeWidth={2} strokeLinecap="round" />
|
|
505
|
+
</svg>
|
|
506
|
+
<div style={{ width: 130, flex: 'none', textAlign: 'right' }}>
|
|
507
|
+
<div style={{ fontSize: 21, fontWeight: 600, letterSpacing: '-0.02em', fontVariantNumeric: 'tabular-nums' }}>{r.val}</div>
|
|
508
|
+
<div style={{ marginTop: 3, fontSize: 13, color: p.ink3 }}>{r.unit}</div>
|
|
509
|
+
</div>
|
|
510
|
+
<div style={{ width: 150, flex: 'none', display: 'flex', alignItems: 'center', gap: 8, fontSize: 14.5, color: p.ink2, whiteSpace: 'nowrap' }}>
|
|
511
|
+
<Ic name="clock" size={16} color={p.ink3} />
|
|
512
|
+
{r.due}
|
|
513
|
+
</div>
|
|
514
|
+
<div style={{ marginLeft: 'auto', width: 120, flex: 'none' }}>
|
|
515
|
+
<div style={{ display: 'flex', fontSize: 13, color: p.ink3, marginBottom: 8 }}>
|
|
516
|
+
<span>Progress</span>
|
|
517
|
+
<span style={{ marginLeft: 'auto', color: p.ink2, fontWeight: 500, fontVariantNumeric: 'tabular-nums' }}>{r.pct}%</span>
|
|
518
|
+
</div>
|
|
519
|
+
<div style={{ height: 6, borderRadius: 3, background: p.fill2, overflow: 'hidden' }}>
|
|
520
|
+
<div style={{ width: `${r.pct}%`, height: '100%', borderRadius: 3, background: live ? p.accent : p.chart2 }} />
|
|
521
|
+
</div>
|
|
522
|
+
</div>
|
|
523
|
+
</div>
|
|
524
|
+
);
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
// 假 dashboard 面板:A = 3×2 卡片网格,B = 列表行。1920×1080。
|
|
528
|
+
// 几何(demo 依赖,勿改):侧栏 220;顶栏 72;A 卡 524×454 @ x=256/808/1360, y=108/590;
|
|
529
|
+
// B 行 1628×171.2 @ y=108+i×191.2,行首 44px 图标块圆心 x=308。
|
|
530
|
+
export const FakeDashboard: React.FC<{ variant?: 'A' | 'B'; tone?: Tone }> = ({ variant = 'A', tone = 'light' }) => {
|
|
531
|
+
const p = P[tone];
|
|
532
|
+
const active = variant === 'A' ? 0 : 2; // 侧栏高亮项与顶栏页名一致
|
|
533
|
+
return (
|
|
534
|
+
<div style={{ width: 1920, height: 1080, background: p.canvas, display: 'flex', fontFamily: SANS }}>
|
|
535
|
+
{/* 侧栏 */}
|
|
536
|
+
<div style={{
|
|
537
|
+
width: 220, background: p.side, padding: '28px 22px', boxSizing: 'border-box', display: 'flex',
|
|
538
|
+
flexDirection: 'column', gap: 6, boxShadow: `inset -1px 0 0 ${p.sideLine}`, flex: 'none',
|
|
539
|
+
}}>
|
|
540
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 11, marginBottom: 22 }}>
|
|
541
|
+
<div style={{
|
|
542
|
+
width: 40, height: 40, borderRadius: 10, flex: 'none', display: 'grid', placeItems: 'center',
|
|
543
|
+
background: 'linear-gradient(145deg, #2a2a26 0%, #171714 100%)',
|
|
544
|
+
boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.14), 0 0 0 1px rgba(0,0,0,0.25)',
|
|
545
|
+
}}>
|
|
546
|
+
<ShotcraftMark size={26} tone="dark" />
|
|
547
|
+
</div>
|
|
548
|
+
<div style={{ minWidth: 0 }}>
|
|
549
|
+
<div style={{ fontSize: 15, fontWeight: 650, color: p.sideInk, letterSpacing: '0.01em' }}>video-shotcraft</div>
|
|
550
|
+
<div style={{ fontSize: 12, color: p.sideInk3, marginTop: 2 }}>Pro plan</div>
|
|
551
|
+
</div>
|
|
552
|
+
</div>
|
|
553
|
+
{NAV.map(([icon, label], i) => (
|
|
554
|
+
<div key={label} style={{
|
|
555
|
+
height: 34, borderRadius: 8, display: 'flex', alignItems: 'center', gap: 11, padding: '0 10px',
|
|
556
|
+
background: i === active ? p.sideFill : 'transparent', color: i === active ? p.sideInk : p.sideInk2,
|
|
557
|
+
fontSize: 14, fontWeight: i === active ? 550 : 450, boxShadow: i === active ? 'inset 0 1px 0 rgba(255,255,255,0.05)' : 'none',
|
|
558
|
+
}}>
|
|
559
|
+
<Ic name={icon} size={16} color={i === active ? p.sideInk : p.sideInk2} />
|
|
560
|
+
{label}
|
|
561
|
+
</div>
|
|
562
|
+
))}
|
|
563
|
+
<div style={{ marginTop: 26, padding: '0 10px', fontSize: 11, fontWeight: 600, letterSpacing: '0.08em', color: p.sideInk3 }}>PROJECTS</div>
|
|
564
|
+
{['Launch film', 'Feature tour', 'Release reel'].map((n, i) => (
|
|
565
|
+
<div key={n} style={{ height: 32, display: 'flex', alignItems: 'center', gap: 11, padding: '0 10px', fontSize: 14, color: p.sideInk2 }}>
|
|
566
|
+
<div style={{ width: 8, height: 8, borderRadius: 3, margin: '0 4px', background: i === 0 ? p.accent : `rgba(255,255,255,${0.42 - i * 0.12})` }} />
|
|
567
|
+
{n}
|
|
568
|
+
</div>
|
|
569
|
+
))}
|
|
570
|
+
<div style={{ marginTop: 'auto', padding: 12, borderRadius: 10, background: 'rgba(255,255,255,0.04)', boxShadow: `inset 0 0 0 1px ${p.sideLine}` }}>
|
|
571
|
+
<div style={{ display: 'flex', fontSize: 12, color: p.sideInk2 }}>
|
|
572
|
+
<span>Usage</span>
|
|
573
|
+
<span style={{ marginLeft: 'auto', color: p.sideInk, fontVariantNumeric: 'tabular-nums' }}>68%</span>
|
|
574
|
+
</div>
|
|
575
|
+
<div style={{ marginTop: 8, height: 4, borderRadius: 2, background: 'rgba(255,255,255,0.08)' }}>
|
|
576
|
+
<div style={{ width: '68%', height: '100%', borderRadius: 2, background: 'rgba(255,255,255,0.7)' }} />
|
|
577
|
+
</div>
|
|
578
|
+
</div>
|
|
579
|
+
</div>
|
|
580
|
+
<div style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
|
|
581
|
+
{/* 顶栏 */}
|
|
582
|
+
<div style={{
|
|
583
|
+
height: 72, background: p.raised, borderBottom: `1px solid ${p.line}`, display: 'flex', alignItems: 'center',
|
|
584
|
+
padding: '0 32px', gap: 20, boxSizing: 'border-box', flex: 'none',
|
|
585
|
+
}}>
|
|
586
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 15, whiteSpace: 'nowrap' }}>
|
|
587
|
+
<span style={{ color: p.ink3 }}>video-shotcraft</span>
|
|
588
|
+
<Ic name="chevron" size={14} color={p.ink3} />
|
|
589
|
+
<span style={{ color: p.ink, fontWeight: 600, letterSpacing: '-0.01em' }}>{variant === 'A' ? 'Overview' : 'Projects'}</span>
|
|
590
|
+
</div>
|
|
591
|
+
<div style={{ display: 'flex', padding: 3, borderRadius: 9, background: p.fill, boxShadow: `inset 0 0 0 1px ${p.line}`, fontSize: 13, marginLeft: 12 }}>
|
|
592
|
+
{['7d', '30d', '90d'].map((t, i) => (
|
|
593
|
+
<div key={t} style={{
|
|
594
|
+
padding: '5px 11px', borderRadius: 6, color: i === 1 ? p.ink : p.ink3, fontWeight: i === 1 ? 550 : 450,
|
|
595
|
+
background: i === 1 ? p.surface : 'transparent', boxShadow: i === 1 ? `0 1px 2px rgba(16,18,24,0.08), inset 0 0 0 1px ${p.line}` : 'none',
|
|
596
|
+
}}>{t}</div>
|
|
597
|
+
))}
|
|
598
|
+
</div>
|
|
599
|
+
<div style={{
|
|
600
|
+
marginLeft: 'auto', height: 36, width: 320, background: p.surface, border: `1px solid ${p.line2}`, borderRadius: 10,
|
|
601
|
+
boxSizing: 'border-box', display: 'flex', alignItems: 'center', gap: 9, padding: '0 8px 0 12px', flex: 'none',
|
|
602
|
+
boxShadow: tone === 'light' ? '0 1px 2px rgba(16,18,24,0.04)' : 'none',
|
|
603
|
+
}}>
|
|
604
|
+
<Ic name="search" size={15} color={p.ink3} />
|
|
605
|
+
<span style={{ fontSize: 14, color: p.ink3 }}>Search…</span>
|
|
606
|
+
<span style={{
|
|
607
|
+
marginLeft: 'auto', fontSize: 11.5, fontWeight: 500, color: p.ink3, padding: '2px 6px', borderRadius: 5,
|
|
608
|
+
boxShadow: `inset 0 0 0 1px ${p.line2}`, background: p.fill,
|
|
609
|
+
}}>⌘K</span>
|
|
610
|
+
</div>
|
|
611
|
+
<div style={{
|
|
612
|
+
width: 36, height: 36, borderRadius: 18, flex: 'none', display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
613
|
+
background: 'linear-gradient(145deg, #3a3c44, #23242a)', color: 'rgba(255,255,255,0.92)', fontSize: 13, fontWeight: 600,
|
|
614
|
+
boxShadow: `0 0 0 2px ${p.raised}, 0 0 0 3px ${p.line2}`,
|
|
615
|
+
}}>JL</div>
|
|
616
|
+
</div>
|
|
617
|
+
{variant === 'A' ? (
|
|
618
|
+
<div style={{ flex: 1, padding: 36, display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gridAutoRows: '1fr', gap: 28, boxSizing: 'border-box' }}>
|
|
619
|
+
{Array.from({ length: 6 }).map((_, i) => (
|
|
620
|
+
<Card key={i} w={524} h={454} seed={i + 1} tone={tone} style={{ width: '100%', height: '100%' }} />
|
|
621
|
+
))}
|
|
622
|
+
</div>
|
|
623
|
+
) : (
|
|
624
|
+
<div style={{ flex: 1, padding: 36, display: 'flex', flexDirection: 'column', gap: 20, boxSizing: 'border-box' }}>
|
|
625
|
+
{Array.from({ length: 5 }).map((_, i) => (
|
|
626
|
+
<ListRow key={i} i={i} p={p} />
|
|
627
|
+
))}
|
|
628
|
+
</div>
|
|
629
|
+
)}
|
|
630
|
+
</div>
|
|
631
|
+
</div>
|
|
632
|
+
);
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
// 标题字块:系统字体栈 + 700 字重 + 随字号的负字距(全大写放松字距),默认 88 号,占位面积与旧版相当
|
|
636
|
+
export const TitleBlock: React.FC<{ text: string; size?: number }> = ({ text, size = 88 }) => {
|
|
637
|
+
const caps = /[A-Z]/.test(text) && text === text.toUpperCase();
|
|
638
|
+
return (
|
|
639
|
+
<div style={{
|
|
640
|
+
fontFamily: SANS, fontWeight: 700, fontSize: size, color: G.ink1, lineHeight: 1.12,
|
|
641
|
+
letterSpacing: caps ? '-0.012em' : '-0.032em', fontVariantNumeric: 'tabular-nums',
|
|
642
|
+
fontKerning: 'normal',
|
|
643
|
+
}}>
|
|
644
|
+
{text}
|
|
645
|
+
</div>
|
|
646
|
+
);
|
|
647
|
+
};
|