Skip to content
On this page

Interface for services

html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>yun yu yuan</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.19.1/TweenMax.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/stats.js/r16/Stats.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.2.47/vue.global.prod.min.js"></script>
    <style>
        html,
        body {
            width: 100%;
            height: 100%;
            overflow: hidden;
        }

        body {
            background: #e0e6eb;
            position: relative;
        }

        * {
            margin: 0;
            padding: 0;
        }

        #polygon,
        #wrapper {
            position: fixed;
            top: 0;
            left: 0;
            display: block;
            width: 100%;
            height: 100%;
        }

        #polygon g {
            mix-blend-mode: lighten;
        }

        #polygon polygon {
            stroke: none;
            fill: white;
        }

        #wrapper {
            display: block;
            height: 100%;
            overflow-y: scroll;
        }

        #wrapper:before {
            display: inline-block;
            width: 0;
            height: 100%;
            vertical-align: middle;
            content: "";
        }

        #center-body {
            display: inline-block;
            vertical-align: middle;
            width: calc(100vw - 32px);
        }

        #content {
            width: 100%;
        }

        .units {
            display: flex;
            align-items: center;
            align-content: center;
            justify-content: center;
            flex-wrap: wrap;
            border: 1px solid #a7a7a7;
            border-radius: 10px;
            margin: 20px 10px 0 10px;
        }

        .units>p {
            text-decoration: none;
            display: block;
            width: 100vw;
            text-align: center;
            font-size: 24px;
            font-weight: bold;
            padding: 10px;
        }

        #content .units a {
            font-size: 20px;
            color: black;
            padding: 10px 30px;
            border: 1px solid white;
            border-radius: 6px;
            text-decoration: none;
            background: transparent;
            transition: all .1s linear;
            margin: 10px;
        }

        #content .units a:not(.disabled):hover {
            background: black;
            color: white;
            border-color: white;
        }

        #content .units a.disabled {
            cursor: not-allowed;
            background: rgb(173 173 173);
            color: black;
            border-color: #919191;
        }

        .switch-container {
            margin: 10px 0;
            display: flex;
            align-items: center;
            justify-content: center;
        }
        .switch-container span {
            cursor: pointer;
            font-size: 18px;
            line-height: 20px;
            padding: 6px 10px;
            border-radius: 8px;
            transition: all .15s linear;
            margin: 0 8px;
            background: white;
            color: black;
        }
        .switch-container span:hover {
            background: rgb(224, 224, 224);
        }
        .switch-container span.active {
            background: aqua;
        }
    </style>
</head>

<body>
    <svg id="polygon" viewBox="0 0 1600 600" preserveAspectRatio="xMidYMid slice">
        <defs>
            <linearGradient id="grad1" x1="0" y1="0" x2="1" y2="0" color-interpolation="sRGB">
                <stop id="stop1a" offset="0%" stop-color="#12a3b4"></stop>
                <stop id="stop1b" offset="100%" stop-color="#ff509e"></stop>
            </linearGradient>
            <linearGradient id="grad2" x1="0" y1="0" x2="1" y2="0" color-interpolation="sRGB">
                <stop id="stop2a" offset="0%" stop-color="#e3bc13"></stop>
                <stop id="stop2b" offset="100%" stop-color="#00a78f"></stop>
            </linearGradient>
        </defs>
        <rect id="rect1" x="0" y="0" width="1600" height="600" stroke="none" fill="url(#grad1)"></rect>
        <rect id="rect2" x="0" y="0" width="1600" height="600" stroke="none" fill="url(#grad2)"></rect>
    </svg>
    <div id="wrapper">
        <div id="center-body">
            <div id="content">
                <div class="switch-container">
                    <span @click="currentType='direct'" :class="{active: currentType==='direct'}">直连</span>
                    <span @click="currentType='cf'" :class="{active: currentType==='cf'}">cf tunnel</span>
                    <span @click="currentType='ts'" :class="{active: currentType==='ts'}">tailscale</span>
                    <span @click="currentType='cdn'" :class="{active: currentType==='cdn'}">cf cdn</span>
                    <span @click="currentType='vpn'" :class="{active: currentType==='vpn'}">vpn</span>
                </div>
                <div class="units" v-for="machine of machines">
                    <p>{{ machine.name }}</p>
                    <a v-for="domain of machine.domains" :href="domain.domain.value || null" :class="{disabled: !domain.domain.value}" @click="clickEvent($event, domain)">{{ domain.name }}</a>
                </div>
            </div>
        </div>
    </div>
</body>
<script>
    Vue.createApp({
        setup() {
            const currentType = Vue.ref('direct');
            fetch('https://ipv6-test.yunyuyuan.net').then((res) => {
                if (res.status !== 200) {
                    currentType.value = 'cf';
                }
            }).catch(() => {
                currentType.value = 'cf';
            })
            
            function getDomainRef(direct, cf='', ts='', vpn='', cdn='') {
                return Vue.computed(() => {
                    switch (currentType.value) {
                        case 'direct':
                            return direct;
                        case 'cf':
                            return cf;
                        case 'ts':
                            return ts;
                        case 'vpn':
                            return vpn;
                        case 'cdn':
                            return cdn;
                    }
                })
            }
            
            function commonRef(s, withPort = true) {
                        return getDomainRef(`https://${s}-d.yunyuyuan.net${withPort ? ':8888': ''}`, 
                                    `https://${s}-cf.yunyuyuan.net`,
                                    `https://${s}-ts.yunyuyuan.net`,
                                    `https://${s}-v.yunyuyuan.net`, withPort ? '' : `https://${s}-cdn.yunyuyuan.net`);
            }
            
            function sameRef(s) {
                return getDomainRef(s, s, s, s, s);
            }
            
            function clickEvent(e, domain) {
                if(!domain.domain.value || domain.click){
                    e.preventDefault();
                    if (domain.click) {
                        domain.click(domain.domain.value);
                    }
                }
            }
            
            function getSubscription(s) {
                const pwd = prompt('输入密码');
                if (pwd) {
                    const link = document.createElement('a');
                    link.href = `${s}/yunyuyuan-${pwd}`;
                    link.target = '_blank';
                    document.body.appendChild(link);
                    link.click();
                    link.remove();
                }
            }
            
            return {
                currentType,
                clickEvent,
                machines: [
                    {
                        name: 'Manjaro',
                        domains: [
                            {
                                name: '云盘',
                                domain: commonRef('file'),
                            },
                            {
                                name: '视频',
                                domain: commonRef('video'),
                            },
                            {
                                name: 'bitTorrent',
                                domain: commonRef('download'),
                            },
                            {
                                name: 'money',
                                domain: commonRef('money'),
                            },
                            {
                                name: 'aria2',
                                domain: commonRef('aria'),
                            },
                            {
                                name: '状态',
                                domain: commonRef('status'),
                            },
                            {
                                name: '测速',
                                domain: commonRef('speed'),
                            },
                            {
                                name: 'filebrowser',
                                domain: commonRef('fb'),
                            },
                            {
                                name: 'gitea',
                                domain: commonRef('git'),
                            },
                            {
                                name: 'code-server',
                                domain: commonRef('code-server'),
                            },
                            {
                                name: 'ttyd',
                                domain: commonRef('ttyd'),
                            },
                            {
                                name: 'novnc',
                                domain: commonRef('novnc'),
                            },
                            {
                                name: 'SSH-CF',
                                domain: getDomainRef('', `https://ssh-cf.yunyuyuan.net`, '', '', ''),
                            },
                            {
                                name: 'VNC-CF',
                                domain: getDomainRef('', `https://vnc-cf.yunyuyuan.net`, '', '', ''),
                            },
                        ],
                    },
                    {
                        name: 'Scaleway ipv6',
                        domains: [
                            {
                                name: '机场',
                                domain: commonRef('v2ray-sw', false),
                                click: getSubscription,
                            },
                            {
                                name: 'ttyd',
                                domain: commonRef('ttyd-sw', false),
                            },
                            {
                                name: '测速',
                                domain: commonRef('speed-sw', false),
                            },
                            {
                                name: 'oss filebrowser',
                                domain: commonRef('fb-sw', false),
                            },
                            {
                                name: 'SSH-CF',
                                domain: getDomainRef('', `https://ssh-sw-cf.yunyuyuan.net`, '', '', ''),
                            },
                        ],
                    },
                    {
                        name: 'Raspberry Pi',
                        domains: [
                            {
                                name: '监控',
                                domain: commonRef('live'),
                            },
                            {
                                name: 'ttyd',
                                domain: commonRef('ttyd-pi'),
                            },
                            {
                                name: 'SSH-CF',
                                domain: getDomainRef('', `https://ssh-pi-cf.yunyuyuan.net`, '', '', ''),
                            },
                            {
                                name: 'VNC-CF',
                                domain: getDomainRef('', `https://vnc-pi-cf.yunyuyuan.net`, '', '', ''),
                            },
                        ],
                    },
                    {
                        name: 'Router',
                        domains: [
                            {
                                name: '路由器',
                                domain: getDomainRef('http://router-d.yunyuyuan.net:8888', '', '', 'http://router-v.yunyuyuan.net', ''),
                            },
                            {
                                name: 'ttyd',
                                domain: getDomainRef('http://router-d.yunyuyuan.net:7681', '', '', 'http://router-v.yunyuyuan.net:7681', ''),
                            },
                        ],
                    },
                    {
                        name: 'Frp',
                        domains: [
                            {
                                name: '控制台',
                                domain: sameRef('https://frp.halberd.cn'),
                            },
                            {
                                name: '云盘',
                                domain: sameRef('https://file.halberd.cn'),
                            },
                            {
                                name: '文件',
                                domain: sameRef('https://fb.halberd.cn'),
                            },
                            {
                                name: 'bitTorrent',
                                domain: sameRef('https://download.halberd.cn'),
                            },
                            {
                                name: '路由器',
                                domain: sameRef('https://router.halberd.cn'),
                            },
                        ],
                    },
                ]
            }
        }
    }).mount('#content')

    function init(showStats) {
        // stats
        if (showStats) {
            var stats = new Stats();
            stats.domElement.style.position = 'absolute';
            stats.domElement.style.left = '0';
            stats.domElement.style.top = '0';
            document.body.appendChild(stats.domElement);
            requestAnimationFrame(function updateStats() {
                stats.update();
                requestAnimationFrame(updateStats);
            });
        }

        // init
        var svg = document.getElementById('polygon');
        tesselation.setup(svg);
        gradients.setup();

        var lastTransitionAt, transitionDelay = 5500, transitionDuration = 3000;

        function playNextTransition() {
            tesselation.next(transitionDuration);
            gradients.next(transitionDuration);
        };

        function tick(time) {
            if (!lastTransitionAt || time - lastTransitionAt > transitionDelay) {
                lastTransitionAt = time;
                playNextTransition();
            }
            window.requestAnimationFrame(tick);
        }
        window.requestAnimationFrame(tick);
    }

    //////////////////////////////
    // Delaunay Triangulation
    //////////////////////////////

    var calcDelaunayTriangulation = (function () {
        var EPSILON = 1.0 / 1048576.0;
        function getSuperT(vertices) {
            var xMin = Number.POSITIVE_INFINITY, yMin = Number.POSITIVE_INFINITY,
                xMax = Number.NEGATIVE_INFINITY, yMax = Number.NEGATIVE_INFINITY,
                i, xDiff, yDiff, maxDiff, xCenter, yCenter;
            for (i = vertices.length; i--;) {
                if (vertices[i][0] < xMin) xMin = vertices[i][0];
                if (vertices[i][0] > xMax) xMax = vertices[i][0];
                if (vertices[i][1] < yMin) yMin = vertices[i][1];
                if (vertices[i][1] > yMax) yMax = vertices[i][1];
            }
            xDiff = xMax - xMin;
            yDiff = yMax - yMin;
            maxDiff = Math.max(xDiff, yDiff);
            xCenter = xMin + xDiff * 0.5;
            yCenter = yMin + yDiff * 0.5;
            return [
                [xCenter - 20 * maxDiff, yCenter - maxDiff],
                [xCenter, yCenter + 20 * maxDiff],
                [xCenter + 20 * maxDiff, yCenter - maxDiff]
            ];
        }
        function circumcircle(vertices, i, j, k) {
            var xI = vertices[i][0], yI = vertices[i][1],
                xJ = vertices[j][0], yJ = vertices[j][1],
                xK = vertices[k][0], yK = vertices[k][1],
                yDiffIJ = Math.abs(yI - yJ), yDiffJK = Math.abs(yJ - yK),
                xCenter, yCenter, m1, m2, xMidIJ, xMidJK, yMidIJ, yMidJK, xDiff, yDiff;
            // bail condition
            if (yDiffIJ < EPSILON && yDiffJK < EPSILON)
                throw new Error("Can't get circumcircle since all 3 points are y-aligned");
            // calc circumcircle center x/y, radius
            m1 = -((xJ - xI) / (yJ - yI));
            m2 = -((xK - xJ) / (yK - yJ));
            xMidIJ = (xI + xJ) / 2.0;
            xMidJK = (xJ + xK) / 2.0;
            yMidIJ = (yI + yJ) / 2.0;
            yMidJK = (yJ + yK) / 2.0;
            xCenter = (yDiffIJ < EPSILON) ? xMidIJ :
                (yDiffJK < EPSILON) ? xMidJK :
                    (m1 * xMidIJ - m2 * xMidJK + yMidJK - yMidIJ) / (m1 - m2);
            yCenter = (yDiffIJ > yDiffJK) ?
                m1 * (xCenter - xMidIJ) + yMidIJ :
                m2 * (xCenter - xMidJK) + yMidJK;
            xDiff = xJ - xCenter;
            yDiff = yJ - yCenter;
            // return
            return { i: i, j: j, k: k, x: xCenter, y: yCenter, r: xDiff * xDiff + yDiff * yDiff };
        }
        function dedupeEdges(edges) {
            var i, j, a, b, m, n;
            for (j = edges.length; j;) {
                b = edges[--j]; a = edges[--j];
                for (i = j; i;) {
                    n = edges[--i]; m = edges[--i];
                    if ((a === m && b === n) || (a === n && b === m)) {
                        edges.splice(j, 2); edges.splice(i, 2);
                        break;
                    }
                }
            }
        }
        return function (vertices) {
            var n = vertices.length,
                i, j, indices, st, candidates, locked, edges, dx, dy, a, b, c;
            // bail if too few / too many verts
            if (n < 3 || n > 2000)
                return [];
            // copy verts and sort indices by x-position
            vertices = vertices.slice(0);
            indices = new Array(n);
            for (i = n; i--;)
                indices[i] = i;
            indices.sort(function (i, j) {
                return vertices[j][0] - vertices[i][0];
            });
            // supertriangle
            st = getSuperT(vertices);
            vertices.push(st[0], st[1], st[2]);
            // init candidates/locked tris list
            candidates = [circumcircle(vertices, n + 0, n + 1, n + 2)];
            locked = [];
            edges = [];
            // scan left to right
            for (i = indices.length; i--; edges.length = 0) {
                c = indices[i];
                // check candidates tris against point
                for (j = candidates.length; j--;) {
                    // lock tri if point to right of circumcirc
                    dx = vertices[c][0] - candidates[j].x;
                    if (dx > 0.0 && dx * dx > candidates[j].r) {
                        locked.push(candidates[j]);
                        candidates.splice(j, 1);
                        continue;
                    }
                    // point outside circumcirc = leave candidates
                    dy = vertices[c][1] - candidates[j].y;
                    if (dx * dx + dy * dy - candidates[j].r > EPSILON)
                        continue;
                    // point inside circumcirc = break apart, save edges
                    edges.push(
                        candidates[j].i, candidates[j].j,
                        candidates[j].j, candidates[j].k,
                        candidates[j].k, candidates[j].i
                    );
                    candidates.splice(j, 1);
                }
                // new candidates from broken edges
                dedupeEdges(edges);
                for (j = edges.length; j;) {
                    b = edges[--j];
                    a = edges[--j];
                    candidates.push(circumcircle(vertices, a, b, c));
                }
            }
            // close candidates tris, remove tris touching supertri verts
            for (i = candidates.length; i--;)
                locked.push(candidates[i]);
            candidates.length = 0;
            for (i = locked.length; i--;)
                if (locked[i].i < n && locked[i].j < n && locked[i].k < n)
                    candidates.push(locked[i].i, locked[i].j, locked[i].k);
            // done
            return candidates;
        };
    })();

    var tesselation = (function () {
        var svg, svgW, svgH, prevGroup;

        function createRandomTesselation() {
            var wW = window.innerWidth;
            var wH = window.innerHeight;

            var gridSpacing = 250, scatterAmount = 0.75;
            var gridSize, i, x, y;

            if (wW / wH > svgW / svgH) { // window wider than svg = use width for gridSize
                gridSize = gridSpacing * svgW / wW;
            } else { // window taller than svg = use height for gridSize
                gridSize = gridSpacing * svgH / wH;
            }

            var vertices = [];
            var xOffset = (svgW % gridSize) / 2, yOffset = (svgH % gridSize) / 2;
            for (x = Math.floor(svgW / gridSize) + 1; x >= -1; x--) {
                for (y = Math.floor(svgH / gridSize) + 1; y >= -1; y--) {
                    vertices.push(
                        [
                            xOffset + gridSize * (x + scatterAmount * (Math.random() - 0.5)),
                            yOffset + gridSize * (y + scatterAmount * (Math.random() - 0.5))
                        ]
                    );
                }
            }

            var triangles = calcDelaunayTriangulation(vertices);

            var group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
            var polygon;
            for (i = triangles.length; i;) {
                polygon = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
                polygon.setAttribute('points',
                    vertices[triangles[--i]][0] + ',' + vertices[triangles[i]][1] + ' ' +
                    vertices[triangles[--i]][0] + ',' + vertices[triangles[i]][1] + ' ' +
                    vertices[triangles[--i]][0] + ',' + vertices[triangles[i]][1]
                );
                group.appendChild(polygon);
            }

            return group;
        }

        return {
            setup: function (svgElement) {
                svg = svgElement;
                var vb = svg.getAttribute('viewBox').split(/\D/g);
                svgW = vb[2];
                svgH = vb[3];
            },
            next: function (t) {
                var toRemove, i, n;
                t /= 1000;

                if (prevGroup && prevGroup.children && prevGroup.children.length) {
                    toRemove = prevGroup;
                    n = toRemove.children.length;
                    for (i = n; i--;) {
                        TweenMax.to(toRemove.children[i], t * 0.4, { opacity: 0, delay: t * (0.3 * i / n) });
                    }
                    TweenMax.delayedCall(t * (0.7 + 0.05), function (group) { svg.removeChild(group); }, [toRemove], this);
                }
                var g = createRandomTesselation();
                n = g.children.length;
                for (i = n; i--;) {
                    TweenMax.fromTo(g.children[i], t * 0.4, { opacity: 0 }, { opacity: 0.3 + 0.25 * Math.random(), delay: t * (0.3 * i / n + 0.3), ease: Back.easeOut });
                }
                svg.appendChild(g);
                prevGroup = g;
            }
        }
    })();

    //////////////////////////////
    // Gradients
    //////////////////////////////

    var gradients = (function () {
        var grad1, grad2, showingGrad1;

        // using colors from IBM Design Colors this time
        var colors = [ // 14 colors - use 3-5 span
            '#3c6df0', // ultramarine50
            '#12a3b4', // aqua40
            '#00a78f', // teal40
            '#00aa5e', // green40
            '#81b532', // lime30
            '#e3bc13', // yellow20
            '#ffb000', // gold20
            '#fe8500', // orange30
            '#fe6100', // peach40
            '#e62325', // red50
            '#dc267f', // magenta50
            '#c22dd5', // purple50
            '#9753e1', // violet50
            '#5a3ec8'  // indigo60
        ];

        function assignRandomColors(gradObj) {
            var rA = Math.floor(colors.length * Math.random());
            var rB = Math.floor(Math.random() * 3) + 3; // [3 - 5]
            rB = (rA + (rB * (Math.random() < 0.5 ? -1 : 1)) + colors.length) % colors.length;
            gradObj.stopA.setAttribute('stop-color', colors[rA]);
            gradObj.stopB.setAttribute('stop-color', colors[rB]);
        }

        return {
            setup: function () {
                showingGrad1 = false;
                grad1 = {
                    stopA: document.getElementById('stop1a'),
                    stopB: document.getElementById('stop1b'),
                    rect: document.getElementById('rect1')
                };
                grad2 = {
                    stopA: document.getElementById('stop2a'),
                    stopB: document.getElementById('stop2b'),
                    rect: document.getElementById('rect2')
                };
                grad1.rect.style.opacity = 0;
                grad2.rect.style.opacity = 0;
            },
            next: function (t) {
                t /= 1000;

                var show, hide;
                if (showingGrad1) {
                    hide = grad1;
                    show = grad2;
                } else {
                    hide = grad2;
                    show = grad1;
                }
                showingGrad1 = !showingGrad1;

                TweenMax.to(hide.rect, 0.55 * t, { opacity: 0, delay: 0.2 * t, ease: Sine.easeOut });
                assignRandomColors(show);
                TweenMax.to(show.rect, 0.65 * t, { opacity: 1, ease: Sine.easeIn });
            }
        };
    })();
    init();
</script>

</html>