Files
nexu-io-open-design/plugins/_official/examples/codenest-coding-platform/example.html
elihahah666 a93869ee09 feat(plugins): 25 motionsites example plugins + daemon asset cache (#3873)
* feat(plugins): add 25 motionsites example plugins + daemon asset cache

Add 25 first-party example scenario plugins under
plugins/_official/examples/ (packaged from motionsites.ai templates via the
motionsites-to-open-design-plugin skill): each ships open-design.json,
SKILL.md, and a self-contained rendered example.html fidelity seed. All 25
pass PluginManifestSchema.

Their cross-border CDN images/videos (cloudinary, higgs.ai, motionsites,
cloudfront) are mirrored to Cloudflare R2 (plugin-assets.open-design.ai) and
high-fidelity compressed (732MB -> 302MB), so example.html / query / SKILL.md
reference the fast public origin instead of slow cross-border hosts.

Daemon asset cache (plugin-asset-cache.ts + /api/asset-cache): a same-origin
disk-cached proxy for any external preview media that remains. The preview
URL rewriter now routes external media (src/poster attrs, CSS url(), and JS
string constants) through it, so cross-border assets satisfy the sandbox CSP
(img-src 'self') and load from local cache. SSRF-guarded: http(s) only, no
credentials, and every resolved address must be public.

* fix(asset-cache): tie SSRF validation to the actual outbound connection

The previous guard resolved DNS in assertSafePublicUrl and then let fetch()
resolve again independently, so a DNS-rebinding host could pass the validation
lookup with a public address and steer the real connection to a private one
(e.g. 169.254.169.254). Move the authoritative check to a connection-time
undici Agent lookup (createValidatingLookup): the address that is validated is
the exact address the socket connects to, closing the TOCTOU gap.
assertSafePublicUrl is now a cheap up-front reject (scheme / credentials /
localhost / literal private IP) only. Adds createValidatingLookup unit tests
(public pass, rebinding-to-private reject, all:true any-private reject).

* fix(asset-cache): reject IPv4-mapped IPv6 literals in the private-IP guard

Node's URL parser normalizes a bracketed mapped literal like
`http://[::ffff:127.0.0.1]/` to the hex form `::ffff:7f00:1`, which the old
dotted-decimal regex missed — so a literal mapped host slipped past
assertSafePublicUrl (and literal IPs skip the DNS lookup hook), letting
`/api/asset-cache` target loopback/private IPv4 via its mapped representation.

Canonicalize IPv6 now: expandIpv6() folds `::` compression and any embedded
dotted IPv4 into eight 16-bit groups, and isPrivateAddress() detects the full
`::ffff:0:0/96` mapped range (hex or dotted), feeding the embedded IPv4 back
through the v4 private-range checks. Group-based classification also replaces
the prefix string-matching for ::, ::1, fe80::/10, fc00::/7, ff00::/8.
Regression coverage added for ::ffff:7f00:1 / ::ffff:127.0.0.1 and
http://[::ffff:127.0.0.1]/x.png.

* fix(asset-cache): full link-local range + stream-cap the upstream body

Two SSRF/DoS hardening fixes on the cache path:

- Link-local was matched as the single fe80:: prefix, leaking the rest of
  fe80::/10 (fe90::, febf::, …) through as "public". Classify with a mask
  ((groups[0] & 0xffc0) === 0xfe80); ULA/multicast use masks too now.

- maxBytes was not a hard ceiling for responses without a trustworthy
  Content-Length: arrayBuffer() buffered the whole body before the size check,
  a memory-exhaustion path for a caller-supplied proxy. Stream the body and
  abort the moment the accumulated size exceeds maxBytes, before concatenating.

Tests: fe80::1 / fe90::1 / febf::1 rejected; a no-Content-Length 800-byte
stream against a 16-byte cap rejects with 413 after <10 pulls (proves it stops
reading instead of buffering the full body).

---------

Co-authored-by: qiongyu1999 <2694684348@qq.com>
2026-06-08 07:31:41 +00:00

234 lines
11 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>CodeNest — Launch Your Coding Career</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Plus+Jakarta+Sans:wght@600;700;800&family=Instrument+Serif:ital@0;1&display=swap" rel="stylesheet">
<style>
:root {
--bg: #070b0a;
--accent: #5ed29c;
--transition: all 0.35s cubic-bezier(0.22, 1, 0.36, 1);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }
body {
font-family: 'Inter', -apple-system, system-ui, sans-serif;
background: var(--bg);
color: #fff;
min-height: 100vh;
position: relative;
overflow-x: hidden;
}
img, svg, video { display: block; }
button { font: inherit; cursor: pointer; border: none; background: none; color: inherit; }
a { color: inherit; text-decoration: none; }
/* ───── BACKGROUND VIDEO + OVERLAYS ───── */
.bg-wrap { position: fixed; inset: 0; z-index: 0; overflow: hidden; background: var(--bg); }
#bg-video {
position: absolute; inset: 0; width: 100%; height: 100%;
object-fit: cover; opacity: 0.6;
}
.overlay-left {
position: absolute; inset: 0; pointer-events: none;
background: linear-gradient(to right, #070b0a 0%, rgba(7,11,10,0.55) 35%, transparent 70%);
}
.overlay-bottom {
position: absolute; inset: 0; pointer-events: none;
background: linear-gradient(to top, #070b0a 0%, rgba(7,11,10,0.4) 30%, transparent 60%);
}
/* vertical grid lines at 25/50/75% */
.grid-lines { position: absolute; inset: 0; pointer-events: none; display: none; }
@media (min-width: 900px) { .grid-lines { display: block; } }
.grid-lines span {
position: absolute; top: 0; bottom: 0; width: 1px;
background: rgba(255,255,255,0.10);
}
.grid-lines span:nth-child(1) { left: 25%; }
.grid-lines span:nth-child(2) { left: 50%; }
.grid-lines span:nth-child(3) { left: 75%; }
/* central glow */
.glow { position: absolute; top: 4%; left: 50%; transform: translateX(-50%); pointer-events: none; opacity: 0.85; }
/* ───── SHELL ───── */
.shell { position: relative; z-index: 2; min-height: 100vh; display: flex; flex-direction: column; }
/* ───── HEADER ───── */
header {
position: absolute; top: 0; left: 0; right: 0; z-index: 20;
display: flex; align-items: center; justify-content: space-between;
padding: 28px clamp(20px, 5vw, 64px);
}
.logo { display: flex; align-items: center; gap: 10px; font-weight: 700; font-size: 1.05rem; letter-spacing: -0.02em; }
.logo svg { width: 26px; height: 26px; }
nav.desktop { display: none; gap: 36px; }
@media (min-width: 900px) { nav.desktop { display: flex; } }
nav.desktop a { font-size: 16px; font-weight: 500; color: rgba(255,255,255,0.85); transition: var(--transition); }
nav.desktop a:hover { color: var(--accent); }
.hamburger { display: grid; place-items: center; width: 44px; height: 44px; border-radius: 12px; background: rgba(255,255,255,0.06); }
@media (min-width: 900px) { .hamburger { display: none; } }
/* mobile menu overlay */
.mobile-menu {
position: fixed; inset: 0; z-index: 50; background: rgba(5,8,7,0.97);
backdrop-filter: blur(8px); display: flex; flex-direction: column;
align-items: center; justify-content: center; gap: 32px;
opacity: 0; pointer-events: none; transition: opacity 0.3s ease;
}
.mobile-menu.open { opacity: 1; pointer-events: auto; }
.mobile-menu a { font-size: 1.6rem; font-weight: 700; letter-spacing: 0.02em; }
.mobile-menu a:hover { color: var(--accent); }
.mobile-close { position: absolute; top: 28px; right: clamp(20px,5vw,64px); width: 44px; height: 44px; display: grid; place-items: center; border-radius: 12px; background: rgba(255,255,255,0.06); }
/* ───── HERO ───── */
.hero {
flex: 1; display: flex; flex-direction: column; justify-content: center;
padding: 140px clamp(20px, 5vw, 64px) 80px;
max-width: 1000px;
}
/* liquid glass card */
.glass-card {
position: relative; width: 200px; height: 200px; border-radius: 22px;
transform: translateY(-50px);
padding: 20px 18px; display: flex; flex-direction: column; justify-content: space-between;
background: rgba(255, 255, 255, 0.01);
background-blend-mode: luminosity;
backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.1);
overflow: hidden;
}
.glass-card::before {
content: ''; position: absolute; inset: 0; border-radius: inherit;
padding: 1.4px; pointer-events: none;
background: linear-gradient(180deg, rgba(255,255,255,0.7), rgba(255,255,255,0.05));
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
-webkit-mask-composite: xor; mask-composite: exclude;
}
.glass-card .gc-tag { font-size: 14px; font-weight: 600; color: rgba(255,255,255,0.85); letter-spacing: 0.02em; }
.glass-card .gc-head { font-size: 18px; line-height: 1.25; font-weight: 600; }
.glass-card .gc-head em { font-family: 'Instrument Serif', Georgia, serif; font-style: italic; font-weight: 400; }
.glass-card .gc-desc { font-size: 11px; line-height: 1.4; color: rgba(255,255,255,0.55); }
.eyebrow {
font-family: 'Plus Jakarta Sans', sans-serif; font-weight: 700;
font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase;
color: var(--accent); margin-bottom: 18px;
}
h1 {
font-family: 'Inter', sans-serif; font-weight: 800; text-transform: uppercase;
letter-spacing: -0.03em; line-height: 0.98;
font-size: clamp(40px, 8vw, 72px);
margin-bottom: 22px; max-width: 14ch;
}
h1 .dot { color: var(--accent); }
.lede {
font-size: 14px; line-height: 1.6; color: rgba(255,255,255,0.7);
max-width: 512px; margin-bottom: 36px;
}
.cta {
display: inline-flex; align-items: center; gap: 10px;
background: var(--accent); color: var(--bg);
font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; font-size: 0.85rem;
padding: 14px 26px; border-radius: 999px; transition: var(--transition);
align-self: flex-start;
}
.cta:hover { transform: translateY(-2px); box-shadow: 0 12px 30px rgba(94,210,156,0.3); }
.cta svg { width: 18px; height: 18px; }
</style>
</head>
<body>
<div class="bg-wrap">
<video id="bg-video" autoplay muted loop playsinline></video>
<div class="overlay-left"></div>
<div class="overlay-bottom"></div>
<div class="grid-lines"><span></span><span></span><span></span></div>
<svg class="glow" width="900" height="420" viewBox="0 0 900 420" aria-hidden="true">
<defs>
<filter id="glow-blur" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="25" />
</filter>
<radialGradient id="glow-grad" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#5ed29c" stop-opacity="0.55" />
<stop offset="55%" stop-color="#1f7a5a" stop-opacity="0.25" />
<stop offset="100%" stop-color="#070b0a" stop-opacity="0" />
</radialGradient>
</defs>
<ellipse cx="450" cy="210" rx="400" ry="120" fill="url(#glow-grad)" filter="url(#glow-blur)" />
</svg>
</div>
<div class="shell">
<header>
<div class="logo">
<svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
CodeNest
</div>
<nav class="desktop">
<a href="#">PROJECTS</a>
<a href="#">BLOG</a>
<a href="#">ABOUT</a>
<a href="#">RESUME</a>
</nav>
<button class="hamburger" id="menu-open" aria-label="Open menu">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="18" x2="20" y2="18"/></svg>
</button>
</header>
<div class="mobile-menu" id="mobile-menu">
<button class="mobile-close" id="menu-close" aria-label="Close menu">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
</button>
<a href="#">PROJECTS</a>
<a href="#">BLOG</a>
<a href="#">ABOUT</a>
<a href="#">RESUME</a>
</div>
<main class="hero">
<div class="glass-card">
<div class="gc-tag">[ 2025 ]</div>
<div class="gc-head">Taught by <em>Industry</em> Professionals</div>
<div class="gc-desc">Learn directly from engineers who ship production code every day.</div>
</div>
<div class="eyebrow">Career-Ready Curriculum</div>
<h1>Launch your coding career<span class="dot">.</span></h1>
<p class="lede">Master in-demand coding skills through hands-on projects, real-world tooling, and mentorship from working professionals — built to take you from first commit to first offer.</p>
<a class="cta" href="#">
Get Started
<svg viewBox="0 0 24 24" fill="none" stroke="#070b0a" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>
</a>
</main>
</div>
<script src="https://cdn.jsdelivr.net/npm/hls.js@1.5.13/dist/hls.min.js"></script>
<script>
(function () {
var video = document.getElementById('bg-video');
var src = 'https://stream.mux.com/tLkHO1qZoaaQOUeVWo8hEBeGQfySP02EPS02BmnNFyXys.m3u8';
if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = src;
} else if (window.Hls && window.Hls.isSupported()) {
var hls = new window.Hls({ enableWorker: false });
hls.loadSource(src);
hls.attachMedia(video);
}
var play = function () { var p = video.play(); if (p && p.catch) p.catch(function () {}); };
video.addEventListener('canplay', play);
play();
var menu = document.getElementById('mobile-menu');
document.getElementById('menu-open').addEventListener('click', function () { menu.classList.add('open'); });
document.getElementById('menu-close').addEventListener('click', function () { menu.classList.remove('open'); });
Array.prototype.forEach.call(menu.querySelectorAll('a'), function (a) {
a.addEventListener('click', function () { menu.classList.remove('open'); });
});
})();
</script>
</body>
</html>