(add) index page
4
app.vue
@ -3,6 +3,8 @@ import type { Graph, Thing } from 'schema-dts';
|
||||
|
||||
const { t, locale } = useI18n();
|
||||
const route = useRoute();
|
||||
const colorMode = useColorMode();
|
||||
|
||||
const getDescription = (): string => {
|
||||
if (route.meta.description != null && route.meta.description != "") {
|
||||
return route.meta.description;
|
||||
@ -82,7 +84,7 @@ useHead((): Record<string, any> => ({
|
||||
}));
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<div class="text-slate-700 dark:text-slate-200 bg-slate-100 dark:bg-gray-900">
|
||||
<noscript class="block bg-accent-800 text-white text-center py-1.5 px-3 keep-all relative z-[10005]">Please turn on Javascript from your browser's settings.</noscript>
|
||||
<NuxtLayout>
|
||||
<NuxtPage />
|
||||
|
@ -1,3 +1,24 @@
|
||||
.bi::before,
|
||||
[class^="bi-"]::before,
|
||||
[class*=" bi-"]::before,
|
||||
.bi {
|
||||
display: inline-block;
|
||||
font-family: bootstrap-icons !important;
|
||||
font-style: normal;
|
||||
font-weight: normal !important;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1;
|
||||
vertical-align: -.125em;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.bi {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@tailwind utilities;
|
||||
|
47
assets/data/nav.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { FunctionalComponent } from "nuxt/dist/app/compat/capi";
|
||||
import GHIcon from "bi/github.svg";
|
||||
|
||||
/** ナビゲーションバー アイテム */
|
||||
type NavItem = {
|
||||
/** 翻訳キー */
|
||||
i18n: string;
|
||||
/** リンク先 */
|
||||
to: string;
|
||||
} | {
|
||||
/** アイコン(svgをインポートして貼り付け) */
|
||||
icon: FunctionalComponent;
|
||||
/** リンク先 */
|
||||
to: string;
|
||||
};
|
||||
|
||||
/** ナビゲーションバー コンテンツ */
|
||||
export default <{
|
||||
/** ナビゲーションの真ん中のリンク */
|
||||
center: NavItem[];
|
||||
/**
|
||||
* ナビゲーションの右端のリンク
|
||||
* (SNSとかGithubのリンクとか)
|
||||
*/
|
||||
right: NavItem[];
|
||||
}> {
|
||||
center: [
|
||||
{
|
||||
i18n: '_nav.servers',
|
||||
to: '/servers/',
|
||||
},
|
||||
{
|
||||
i18n: '_nav.docs',
|
||||
to: '/docs/',
|
||||
},
|
||||
{
|
||||
i18n: '_nav.blog',
|
||||
to: '/blog/',
|
||||
}
|
||||
],
|
||||
right: [
|
||||
{
|
||||
icon: GHIcon,
|
||||
to: 'https://github.com/misskey-dev/misskey-hub',
|
||||
},
|
||||
]
|
||||
};
|
24
assets/js/fadein/index.ts
Normal file
@ -0,0 +1,24 @@
|
||||
export const vFadeIn = {
|
||||
mounted: (src, binding, vn) => {
|
||||
src.classList.add('__v_fadeIn_out');
|
||||
src.children[0].style.transition = `all 0.5s ease`;
|
||||
|
||||
function onIntersect(entries) {
|
||||
for (const entry of entries) {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('__v_fadeIn_in');
|
||||
} else {
|
||||
entry.target.classList.remove('__v_fadeIn_in');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const observer = new IntersectionObserver(onIntersect, {
|
||||
root: null,
|
||||
rootMargin: '9999px 0px -300px 0px',
|
||||
threshold: 0,
|
||||
});
|
||||
|
||||
observer.observe(src);
|
||||
}
|
||||
};
|
9
assets/js/parallax/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export const vParallax = {
|
||||
mounted: (src: HTMLElement, binding: Ref<number>) => {
|
||||
src.style.willChange = 'transform';
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
src.style.transform = `translateY(${window.scrollY / binding.value}px)`;
|
||||
}, { passive: true });
|
||||
}
|
||||
}
|
84
assets/js/particles/drop.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import * as THREE from 'three';
|
||||
import * as Calc from './utils/calc';
|
||||
import * as Ease from './utils/ease';
|
||||
import { Loader } from './loader';
|
||||
import { System } from './system';
|
||||
|
||||
export class Drop {
|
||||
private loader: Loader;
|
||||
private system: System;
|
||||
|
||||
private array: Drop[];
|
||||
private group: THREE.Object3D;
|
||||
private x: number;
|
||||
private y: number;
|
||||
private z: number;
|
||||
private size: number;
|
||||
private color: number;
|
||||
private opacity: number;
|
||||
private strength: number;
|
||||
private yBase: number;
|
||||
|
||||
private progress: number = 0;
|
||||
private rate: number = 0.01;
|
||||
|
||||
private geometry: System['boxGeometry'];
|
||||
private material: THREE.MeshBasicMaterial;
|
||||
private mesh: THREE.Mesh;
|
||||
|
||||
constructor(config, system) {
|
||||
this.system = system;
|
||||
this.loader = this.system.loader;
|
||||
|
||||
this.array = config.array;
|
||||
this.group = config.group;
|
||||
this.x = config.x;
|
||||
this.y = config.y;
|
||||
this.z = config.z;
|
||||
this.size = config.size;
|
||||
this.color = config.color;
|
||||
this.opacity = config.opacity;
|
||||
this.strength = config.strength;
|
||||
|
||||
this.yBase = config.y;
|
||||
|
||||
this.createMesh();
|
||||
}
|
||||
|
||||
createMesh() {
|
||||
this.geometry = this.system.boxGeometry;
|
||||
|
||||
this.material = new THREE.MeshBasicMaterial({
|
||||
color: this.color,
|
||||
transparent: true,
|
||||
opacity: this.opacity,
|
||||
depthTest: false,
|
||||
precision: 'lowp'
|
||||
});
|
||||
|
||||
this.mesh = new THREE.Mesh(this.geometry, this.material);
|
||||
|
||||
this.mesh.position.x = this.x;
|
||||
this.mesh.position.y = this.y;
|
||||
this.mesh.position.z = this.z;
|
||||
|
||||
this.mesh.scale.set(this.size, this.size, this.size);
|
||||
|
||||
this.group.add(this.mesh);
|
||||
}
|
||||
|
||||
update(i) {
|
||||
this.progress += this.rate * this.loader.deltaTimeNormal;
|
||||
this.mesh.position.y = this.yBase - Ease.inExpo(this.progress, 0, 1, 1) * this.yBase;
|
||||
this.mesh.scale.set(this.size, this.size + this.size * 16 * Ease.inExpo(this.progress, 0, 1, 1), this.size);
|
||||
this.mesh.material.opacity = Ease.inExpo(this.progress, 0, 1, 1);
|
||||
|
||||
if(this.progress >= 1) {
|
||||
this.geometry.dispose();
|
||||
this.material.dispose();
|
||||
this.group.remove(this.mesh);
|
||||
this.array.splice(i, 1);
|
||||
this.system.createRipple(this.mesh.position.x, this.mesh.position.z, this.strength);
|
||||
}
|
||||
}
|
||||
}
|
117
assets/js/particles/loader.ts
Normal file
@ -0,0 +1,117 @@
|
||||
import * as THREE from 'three';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import { System } from './system';
|
||||
|
||||
export class Loader {
|
||||
public timescale: number = 0.75;
|
||||
public camera: THREE.PerspectiveCamera;
|
||||
public clock: THREE.Clock;
|
||||
public deltaTimeSeconds: number;
|
||||
public deltaTimeMilliseconds: number;
|
||||
public deltaTimeNormal: number;
|
||||
public elapsedMilliseconds: number;
|
||||
public system: System;
|
||||
public scene: THREE.Scene;
|
||||
public renderer: THREE.WebGLRenderer;
|
||||
private diffTime: number;
|
||||
private raf: ReturnType<typeof window.requestAnimationFrame> | null;
|
||||
private dom: HTMLElement;
|
||||
|
||||
constructor(container: HTMLElement) {
|
||||
this.dom = container;
|
||||
|
||||
this.raf = null;
|
||||
|
||||
this.setupTime();
|
||||
this.setupScene();
|
||||
this.setupCamera();
|
||||
this.setupRenderer();
|
||||
this.listen();
|
||||
this.onResize();
|
||||
|
||||
this.system = new System(this, {
|
||||
particleColor: tinycolor(getComputedStyle(this.dom).getPropertyValue('--c-text')).toHexString(),
|
||||
dropColor: tinycolor(getComputedStyle(this.dom).getPropertyValue('--c-brand')).toHexString(),
|
||||
rippleColor: tinycolor(getComputedStyle(this.dom).getPropertyValue('--c-brand')).toHexString(),
|
||||
});
|
||||
this.loop();
|
||||
}
|
||||
|
||||
setupTime() {
|
||||
this.clock = new THREE.Clock();
|
||||
this.deltaTimeSeconds = this.clock.getDelta() * this.timescale;
|
||||
this.deltaTimeMilliseconds = this.deltaTimeSeconds * 1000;
|
||||
this.deltaTimeNormal = this.deltaTimeMilliseconds / (1000 / 60);
|
||||
this.elapsedMilliseconds = 0;
|
||||
}
|
||||
|
||||
setupScene() {
|
||||
this.scene = new THREE.Scene();
|
||||
}
|
||||
|
||||
setupCamera() {
|
||||
this.camera = new THREE.PerspectiveCamera(70, 0, 0.0001, 10000);
|
||||
|
||||
//this.camera.position.x = 0;
|
||||
//this.camera.position.y = 0;
|
||||
//this.camera.position.z = 20;
|
||||
|
||||
this.camera.position.x = 0;
|
||||
this.camera.position.y = -30;
|
||||
this.camera.position.z = 0;
|
||||
}
|
||||
|
||||
setupRenderer() {
|
||||
this.renderer = new THREE.WebGLRenderer({
|
||||
alpha: true,
|
||||
antialias: true
|
||||
});
|
||||
|
||||
this.dom.appendChild(this.renderer.domElement);
|
||||
}
|
||||
|
||||
update() {
|
||||
this.deltaTimeSeconds = this.clock.getDelta();
|
||||
if(this.diffTime) {
|
||||
this.deltaTimeSeconds -= this.diffTime;
|
||||
this.diffTime = 0;
|
||||
}
|
||||
this.deltaTimeSeconds *= this.timescale;
|
||||
this.deltaTimeMilliseconds = this.deltaTimeSeconds * 1000;
|
||||
this.deltaTimeNormal = this.deltaTimeMilliseconds / (1000 / 60);
|
||||
this.elapsedMilliseconds += this.deltaTimeMilliseconds;
|
||||
|
||||
this.system.update();
|
||||
}
|
||||
|
||||
render() {
|
||||
this.renderer.render(this.scene, this.camera);
|
||||
}
|
||||
|
||||
listen() {
|
||||
window.addEventListener('resize', () => this.onResize());
|
||||
}
|
||||
|
||||
onResize() {
|
||||
const width = this.dom.offsetWidth;
|
||||
const height = this.dom.offsetHeight;
|
||||
|
||||
this.camera.aspect = width / height;
|
||||
this.camera.updateProjectionMatrix();
|
||||
|
||||
this.renderer.setPixelRatio(window.devicePixelRatio > 1 ? 2 : 1);
|
||||
this.renderer.setSize(width, height);
|
||||
}
|
||||
|
||||
loop() {
|
||||
this.update();
|
||||
this.render();
|
||||
this.raf = window.requestAnimationFrame(() => this.loop());
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.raf) {
|
||||
window.cancelAnimationFrame(this.raf);
|
||||
}
|
||||
}
|
||||
}
|
82
assets/js/particles/particle.ts
Normal file
@ -0,0 +1,82 @@
|
||||
import * as THREE from 'three';
|
||||
import * as Calc from './utils/calc';
|
||||
import * as Ease from './utils/ease';
|
||||
import { Loader } from './loader';
|
||||
import { System } from './system';
|
||||
|
||||
export class Particle {
|
||||
private loader: Loader;
|
||||
private system: System;
|
||||
|
||||
private lerpFactor = 0.3;
|
||||
private dampFactor = 0.3;
|
||||
|
||||
private group: THREE.Object3D;
|
||||
private x: number;
|
||||
private y: number;
|
||||
private z: number;
|
||||
private size: number;
|
||||
private color: number;
|
||||
private opacity: number;
|
||||
|
||||
public base: THREE.Vector3;
|
||||
public velocity: THREE.Vector3;
|
||||
private geometry: THREE.SphereBufferGeometry;
|
||||
private material: THREE.MeshBasicMaterial;
|
||||
private mesh: THREE.Mesh;
|
||||
|
||||
constructor(config, system) {
|
||||
this.system = system;
|
||||
this.loader = this.system.loader;
|
||||
|
||||
this.group = config.group;
|
||||
this.x = config.x;
|
||||
this.y = config.y;
|
||||
this.z = config.z;
|
||||
this.size = config.size;
|
||||
this.color = config.color;
|
||||
this.opacity = config.opacity;
|
||||
|
||||
this.createMesh();
|
||||
|
||||
this.base = new THREE.Vector3(config.x, config.y, config.z);
|
||||
this.velocity = new THREE.Vector3(0, 0, 0);
|
||||
}
|
||||
|
||||
createMesh() {
|
||||
this.geometry = this.system.sphereGeometry;
|
||||
|
||||
this.material = new THREE.MeshBasicMaterial({
|
||||
color: this.color,
|
||||
transparent: true,
|
||||
opacity: this.opacity,
|
||||
depthTest: false,
|
||||
precision: 'lowp'
|
||||
});
|
||||
|
||||
this.mesh = new THREE.Mesh(this.geometry, this.material);
|
||||
|
||||
this.mesh.position.x = this.x;
|
||||
this.mesh.position.y = this.y;
|
||||
this.mesh.position.z = this.z;
|
||||
|
||||
this.mesh.scale.set(this.size, this.size, this.size);
|
||||
|
||||
this.group.add(this.mesh);
|
||||
}
|
||||
|
||||
update() {
|
||||
const scale = 0.075 + (Math.abs(this.velocity.y) / 25)
|
||||
this.mesh.scale.set(scale, scale, scale);
|
||||
|
||||
//const opacity = 0.15 + (Math.abs(this.velocity.y) / 1)
|
||||
//this.mesh.material.opacity = Calc.clamp(opacity, 0.15, 1);
|
||||
const opacity = 0 + (Math.abs(this.velocity.y) / 1)
|
||||
this.mesh.material.opacity = Calc.clamp(opacity, 0, 1);
|
||||
|
||||
this.velocity.y += (this.base.y - this.mesh.position.y) * this.lerpFactor;
|
||||
this.velocity.multiplyScalar(this.dampFactor);
|
||||
this.mesh.position.add(this.velocity);
|
||||
}
|
||||
|
||||
}
|
95
assets/js/particles/ripple.ts
Normal file
@ -0,0 +1,95 @@
|
||||
import * as THREE from 'three';
|
||||
import { MeshLineGeometry as MeshLine, MeshLineMaterial } from 'meshline';
|
||||
import * as Calc from './utils/calc';
|
||||
import * as Ease from './utils/ease';
|
||||
import { Loader } from './loader';
|
||||
import { System } from './system';
|
||||
|
||||
export class Ripple {
|
||||
private loader: Loader;
|
||||
private system: System;
|
||||
|
||||
private array: Ripple[];
|
||||
private group: THREE.Object3D;
|
||||
private sphere: THREE.Sphere;
|
||||
private strength: number; // 波の高さ
|
||||
private threshold: number;
|
||||
private growth: number;
|
||||
private life: number;
|
||||
private decay: number;
|
||||
private influence: THREE.Vector3;
|
||||
private geometry: MeshLine;
|
||||
private material: THREE.LineBasicMaterial;
|
||||
private mesh: THREE.Mesh;
|
||||
|
||||
constructor(config, system) {
|
||||
this.system = system;
|
||||
this.loader = this.system.loader;
|
||||
|
||||
this.array = config.array;
|
||||
this.group = config.group;
|
||||
this.sphere = new THREE.Sphere(new THREE.Vector3(config.x, config.y, config.z), 0);
|
||||
this.strength = config.strength ? config.strength : Calc.rand(7, 15);
|
||||
this.threshold = Calc.rand(5, 20);
|
||||
this.growth = Calc.rand(0.2, 0.5);
|
||||
this.life = 1;
|
||||
this.decay = Calc.rand(0.01, 0.02);
|
||||
this.influence = new THREE.Vector3();
|
||||
const points = [];
|
||||
const resolution = 64;
|
||||
for (let j = 0; j < (Math.PI * 2) + ((Math.PI) / resolution); j += (2 * Math.PI) / resolution) {
|
||||
points.push(Math.cos(j), Math.sin(j), 0);
|
||||
}
|
||||
this.geometry = new MeshLine();
|
||||
this.geometry.setPoints(points);
|
||||
|
||||
this.material = new MeshLineMaterial({
|
||||
lineWidth: 0.25,
|
||||
color: config.color,
|
||||
opacity: 1,
|
||||
transparent: true,
|
||||
depthTest: false,
|
||||
});
|
||||
this.material.onBeforeRender = () => {};
|
||||
this.mesh = new THREE.Mesh(this.geometry, this.material);
|
||||
this.mesh.rotation.x = Math.PI / 2;
|
||||
this.mesh.position.x = this.sphere.center.x;
|
||||
this.mesh.position.y = 0;
|
||||
this.mesh.position.z = this.sphere.center.z;
|
||||
this.group.add(this.mesh);
|
||||
}
|
||||
|
||||
getInfluenceVector(vec) {
|
||||
this.influence.set(0, 0, 0);
|
||||
let distance = this.sphere.distanceToPoint(vec);
|
||||
|
||||
if(distance <= this.threshold ) {
|
||||
let ease = Ease.inOutSine(this.threshold - distance, 0, 1, this.threshold);
|
||||
let power = (this.strength * ease * this.life);
|
||||
this.influence.addVectors(vec, this.sphere.center).multiplyScalar(power);
|
||||
}
|
||||
|
||||
return this.influence;
|
||||
}
|
||||
|
||||
update(i) {
|
||||
this.sphere.radius += (this.growth * this.life) * this.loader.deltaTimeNormal;
|
||||
this.life -= this.decay * this.loader.deltaTimeNormal;
|
||||
|
||||
this.mesh.position.y = (1 - this.life) * -2;
|
||||
let newScale = 0.001 + this.sphere.radius;
|
||||
this.mesh.scale.set(newScale, newScale, newScale);
|
||||
this.mesh.material.opacity = this.life / 3;
|
||||
|
||||
if(this.life <= 0) {
|
||||
this.destroy(i);
|
||||
}
|
||||
}
|
||||
|
||||
destroy(i) {
|
||||
this.geometry.dispose();
|
||||
this.material.dispose();
|
||||
this.group.remove(this.mesh);
|
||||
this.array.splice(i, 1);
|
||||
}
|
||||
}
|
152
assets/js/particles/system.ts
Normal file
@ -0,0 +1,152 @@
|
||||
import * as THREE from 'three';
|
||||
import * as Calc from './utils/calc';
|
||||
import * as Ease from './utils/ease';
|
||||
import { Drop } from './drop';
|
||||
import { Loader } from './loader';
|
||||
import { Particle } from './particle';
|
||||
import { Ripple } from './ripple';
|
||||
|
||||
function hexToInt(rrggbb: string): number {
|
||||
if (rrggbb.startsWith('#')) rrggbb = rrggbb.substr(1);
|
||||
return parseInt(rrggbb, 16);
|
||||
}
|
||||
|
||||
export class System {
|
||||
public loader: Loader;
|
||||
private drops: Drop[] = [];
|
||||
private ripples: Ripple[] = [];
|
||||
private particles: Particle[] = [];
|
||||
|
||||
public sphereGeometry: THREE.SphereGeometry;
|
||||
public boxGeometry: THREE.BoxGeometry;
|
||||
private center: THREE.Vector3;
|
||||
private particleGroup: THREE.Object3D;
|
||||
|
||||
private size = 96;
|
||||
private cols = 32;
|
||||
private rows = 32;
|
||||
|
||||
private tick: number = 0;
|
||||
private dropTick = 0;
|
||||
private dropTickMin = 25;
|
||||
private dropTickMax = 30;
|
||||
|
||||
private particleColor: number;
|
||||
private rippleColor: number;
|
||||
private dropColor: number;
|
||||
|
||||
constructor(loader, config: { particleColor: string; rippleColor: string; dropColor: string; }) {
|
||||
this.loader = loader;
|
||||
|
||||
this.particleColor = hexToInt(config.particleColor);
|
||||
this.rippleColor = hexToInt(config.rippleColor);
|
||||
this.dropColor = hexToInt(config.dropColor);
|
||||
|
||||
this.sphereGeometry = new THREE.SphereGeometry(1, 16, 16);
|
||||
this.boxGeometry = new THREE.BoxGeometry(1, 1, 1);
|
||||
this.center = new THREE.Vector3();
|
||||
this.loader.camera.lookAt(this.center);
|
||||
|
||||
this.particleGroup = new THREE.Object3D();
|
||||
this.particleGroup.scale.set(1, 1, 1);
|
||||
|
||||
this.loader.scene.add(this.particleGroup);
|
||||
|
||||
for(let col = 0; col < this.cols; col++) {
|
||||
for(let row = 0; row < this.rows; row++) {
|
||||
let x = Calc.map(col, 0, this.cols - 1, -this.size / 2, this.size / 2);
|
||||
let y = 0;
|
||||
let z = Calc.map(row, 0, this.rows - 1, -this.size / 2, this.size / 2);
|
||||
|
||||
this.particles.push(new Particle({
|
||||
group: this.particleGroup,
|
||||
x: x,
|
||||
y: y,
|
||||
z: z,
|
||||
size: 0.01,
|
||||
color: this.particleColor,
|
||||
opacity: 0.01
|
||||
}, this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createDrop(x?, y?, z?, strength?) {
|
||||
this.drops.push(new Drop({
|
||||
array: this.drops,
|
||||
group: this.particleGroup,
|
||||
x: x === undefined ? Calc.rand(-this.size / 2, this.size / 2) : x,
|
||||
y: y === undefined ? Calc.rand(30, 50) : y,
|
||||
z: z === undefined ? Calc.rand(-this.size / 2, this.size / 2) : z,
|
||||
size: 0.15,
|
||||
color: this.dropColor,
|
||||
opacity: 0,
|
||||
strength: strength
|
||||
}, this));
|
||||
}
|
||||
|
||||
updateDrops() {
|
||||
let i = this.drops.length;
|
||||
while(i--) {
|
||||
this.drops[i].update(i);
|
||||
}
|
||||
}
|
||||
|
||||
createRipple(x, z, strength) {
|
||||
this.ripples.push(new Ripple({
|
||||
array: this.ripples,
|
||||
group: this.particleGroup,
|
||||
x: x,
|
||||
y: -0.1,
|
||||
z: z,
|
||||
color: this.rippleColor,
|
||||
strength: strength
|
||||
}, this));
|
||||
}
|
||||
|
||||
updateRipples() {
|
||||
let i = this.ripples.length;
|
||||
while(i--) {
|
||||
this.ripples[i].update(i);
|
||||
}
|
||||
}
|
||||
|
||||
update() {
|
||||
{
|
||||
let i = this.particles.length;
|
||||
while(i--) {
|
||||
this.particles[i].update();
|
||||
}
|
||||
}
|
||||
|
||||
if(this.tick >= this.dropTick) {
|
||||
this.createDrop();
|
||||
this.dropTick = Calc.randInt(this.dropTickMin, this.dropTickMax);
|
||||
this.tick = 0;
|
||||
}
|
||||
|
||||
this.updateDrops();
|
||||
this.updateRipples();
|
||||
|
||||
{
|
||||
let i = this.particles.length;
|
||||
while(i--) {
|
||||
let j = this.ripples.length;
|
||||
while(j--) {
|
||||
let particle = this.particles[i];
|
||||
let ripple = this.ripples[j];
|
||||
let influence = ripple.getInfluenceVector(particle.base);
|
||||
influence.setX(0);
|
||||
influence.setZ(0);
|
||||
particle.velocity.add(influence);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.particleGroup.rotation.x = Math.cos(this.loader.elapsedMilliseconds * 0.0005) * 0.1;
|
||||
this.particleGroup.rotation.y = Math.PI * 0.25 + Math.sin(this.loader.elapsedMilliseconds * 0.0005) * -0.2;
|
||||
|
||||
this.tick += this.loader.deltaTimeNormal;
|
||||
}
|
||||
}
|
||||
|
201
assets/js/particles/utils/calc.ts
Normal file
@ -0,0 +1,201 @@
|
||||
/*
|
||||
------------------------------------------
|
||||
| rand:float - returns random float
|
||||
|
|
||||
| min:number - minimum value
|
||||
| max:number - maximum value
|
||||
| ease:function - easing function to apply to the random value
|
||||
|
|
||||
| Get a random float between two values,
|
||||
| with the option of easing bias.
|
||||
------------------------------------------ */
|
||||
export function rand(min, max?, ease?) {
|
||||
if(max === undefined) {
|
||||
max = min;
|
||||
min = 0;
|
||||
}
|
||||
let random = Math.random();
|
||||
if(ease) {
|
||||
random = ease(Math.random(), 0, 1, 1);
|
||||
}
|
||||
return random * (max - min) + min;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| randInt:integer - returns random integer
|
||||
|
|
||||
| min:number - minimum value
|
||||
| max:number - maximum value
|
||||
| ease:function - easing function to apply to the random value
|
||||
|
|
||||
| Get a random integer between two values,
|
||||
| with the option of easing bias.
|
||||
------------------------------------------ */
|
||||
export function randInt(min, max?, ease?) {
|
||||
if(max === undefined) {
|
||||
max = min;
|
||||
min = 0;
|
||||
}
|
||||
let random = Math.random();
|
||||
if(ease) {
|
||||
random = ease(Math.random(), 0, 1, 1);
|
||||
}
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| randArr:item - returns random iem from array
|
||||
|
|
||||
| arr:array - the array to randomly pull from
|
||||
|
|
||||
| Get a random item from an array.
|
||||
------------------------------------------ */
|
||||
export function randArr(arr) {
|
||||
return arr[Math.floor(Math.random() * arr.length)];
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| map:number - returns a mapped value
|
||||
|
|
||||
| val:number - input value
|
||||
| inputMin:number - minimum of input range
|
||||
| inputMax:number - maximum of input range
|
||||
| outputMin:number - minimum of output range
|
||||
| outputMax:number - maximum of output range
|
||||
|
|
||||
| Get a mapped value from and input min/max
|
||||
| to an output min/max.
|
||||
------------------------------------------ */
|
||||
export function map(val, inputMin, inputMax, outputMin, outputMax) {
|
||||
return ((outputMax - outputMin) * ((val - inputMin) / (inputMax - inputMin))) + outputMin;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| clamp:number - returns clamped value
|
||||
|
|
||||
| val:number - value to be clamped
|
||||
| min:number - minimum of clamped range
|
||||
| max:number - maximum of clamped range
|
||||
|
|
||||
| Clamp a value to a min/max range.
|
||||
------------------------------------------ */
|
||||
export function clamp(val, min, max) {
|
||||
return Math.max(Math.min(val, max), min);
|
||||
}
|
||||
|
||||
export function lerp(current, target, mix) {
|
||||
return current + (target - current) * mix;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| roundToUpperInterval:number - returns rounded up value
|
||||
|
|
||||
| value:number - value to be rounded
|
||||
| interval:number - interval
|
||||
|
|
||||
| Round up a value to the next highest interval.
|
||||
------------------------------------------ */
|
||||
export function roundToUpperInterval(value, interval) {
|
||||
if(value % interval === 0) {
|
||||
value += 0.0001;
|
||||
}
|
||||
return Math.ceil(value / interval) * interval;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| roundDownToInterval:number - returns rounded down value
|
||||
|
|
||||
| value:number - value to be rounded
|
||||
| interval:number - interval
|
||||
|
|
||||
| Round down a value to the next lowest interval.
|
||||
------------------------------------------ */
|
||||
export function roundToLowerInterval(value, interval) {
|
||||
if(value % interval === 0) {
|
||||
value -= 0.0001;
|
||||
}
|
||||
return Math.floor(value / interval) * interval;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| roundToNearestInterval:number - returns rounded value
|
||||
|
|
||||
| value:number - value to be rounded
|
||||
| interval:number - interval
|
||||
|
|
||||
| Round a value to the nearest interval.
|
||||
------------------------------------------ */
|
||||
export function roundToNearestInterval(value, interval) {
|
||||
return Math.round(value / interval) * interval;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| intersectSphere:boolean - returns if intersecting or not
|
||||
|
|
||||
| a:object - sphere 1 with radius, x, y, and z
|
||||
| b:object - sphere 2 with radius, x, y, and z
|
||||
|
|
||||
| Check if two sphere are intersecting
|
||||
| in 3D space.
|
||||
------------------------------------------ */
|
||||
export function intersectSphere(a, b) {
|
||||
let distance = Math.sqrt(
|
||||
(a.x - b.x) * (a.x - b.x) +
|
||||
(a.y - b.y) * (a.y - b.y) +
|
||||
(a.z - b.z) * (a.z - b.z)
|
||||
);
|
||||
return distance < (a.radius + b.radius);
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| getIndexFromCoords:number - returns index
|
||||
|
|
||||
| x:number - x value (column)
|
||||
| y:number - y value (row)
|
||||
| w:number - width of grid
|
||||
|
|
||||
| Convert from grid coords to index.
|
||||
------------------------------------------ */
|
||||
export function getIndexFromCoords(x, y, w) {
|
||||
return x + (y * w);
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| getCoordsFromIndex:object - returns coords
|
||||
|
|
||||
| i:number - index
|
||||
| w:number - width of grid
|
||||
|
|
||||
| Convert from index to grid coords.
|
||||
------------------------------------------ */
|
||||
export function getCoordsFromIndex(i, w) {
|
||||
return {
|
||||
x: i % w,
|
||||
y: Math.floor(i / w)
|
||||
}
|
||||
}
|
||||
|
||||
export function visibleHeightAtZDepth(depth, camera) {
|
||||
// https://discourse.threejs.org/t/functions-to-calculate-the-visible-width-height-at-a-given-z-depth-from-a-perspective-camera/269
|
||||
let cameraOffset = camera.position.z;
|
||||
if ( depth < cameraOffset ) depth -= cameraOffset;
|
||||
else depth += cameraOffset;
|
||||
let vFOV = camera.fov * Math.PI / 180;
|
||||
return 2 * Math.tan( vFOV / 2 ) * Math.abs( depth );
|
||||
}
|
||||
|
||||
export function visibleWidthAtZDepth(depth, camera) {
|
||||
// https://discourse.threejs.org/t/functions-to-calculate-the-visible-width-height-at-a-given-z-depth-from-a-perspective-camera/269
|
||||
let height = visibleHeightAtZDepth( depth, camera );
|
||||
return height * camera.aspect;
|
||||
}
|
433
assets/js/particles/utils/ease.ts
Normal file
@ -0,0 +1,433 @@
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| inQuad:float - returns eased float value
|
||||
|
|
||||
| t:number - current time
|
||||
| b:number - beginning value
|
||||
| c:number - change in value
|
||||
| d:number - duration
|
||||
|
|
||||
| Get an eased float value based on inQuad.
|
||||
------------------------------------------ */
|
||||
export function inQuad(t, b, c, d) {
|
||||
return c*(t/=d)*t + b;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| outQuad:float - returns eased float value
|
||||
|
|
||||
| t:number - current time
|
||||
| b:number - beginning value
|
||||
| c:number - change in value
|
||||
| d:number - duration
|
||||
|
|
||||
| Get an eased float value based on outQuad.
|
||||
------------------------------------------ */
|
||||
export function outQuad(t, b, c, d) {
|
||||
return -c *(t/=d)*(t-2) + b;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| inOutQuad:float - returns eased float value
|
||||
|
|
||||
| t:number - current time
|
||||
| b:number - beginning value
|
||||
| c:number - change in value
|
||||
| d:number - duration
|
||||
|
|
||||
| Get an eased float value based on inOutQuad.
|
||||
------------------------------------------ */
|
||||
export function inOutQuad(t, b, c, d) {
|
||||
if ((t/=d/2) < 1) return c/2*t*t + b;
|
||||
return -c/2 * ((--t)*(t-2) - 1) + b;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| inCubic:float - returns eased float value
|
||||
|
|
||||
| t:number - current time
|
||||
| b:number - beginning value
|
||||
| c:number - change in value
|
||||
| d:number - duration
|
||||
|
|
||||
| Get an eased float value based on inCubic.
|
||||
------------------------------------------ */
|
||||
export function inCubic(t, b, c, d) {
|
||||
return c*(t/=d)*t*t + b;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| outCubic:float - returns eased float value
|
||||
|
|
||||
| t:number - current time
|
||||
| b:number - beginning value
|
||||
| c:number - change in value
|
||||
| d:number - duration
|
||||
|
|
||||
| Get an eased float value based on outCubic.
|
||||
------------------------------------------ */
|
||||
export function outCubic(t, b, c, d) {
|
||||
return c*((t=t/d-1)*t*t + 1) + b;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| inOutCubic:float - returns eased float value
|
||||
|
|
||||
| t:number - current time
|
||||
| b:number - beginning value
|
||||
| c:number - change in value
|
||||
| d:number - duration
|
||||
|
|
||||
| Get an eased float value based on inOutCubic.
|
||||
------------------------------------------ */
|
||||
export function inOutCubic(t, b, c, d) {
|
||||
if ((t/=d/2) < 1) return c/2*t*t*t + b;
|
||||
return c/2*((t-=2)*t*t + 2) + b;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| inQuart:float - returns eased float value
|
||||
|
|
||||
| t:number - current time
|
||||
| b:number - beginning value
|
||||
| c:number - change in value
|
||||
| d:number - duration
|
||||
|
|
||||
| Get an eased float value based on inQuart.
|
||||
------------------------------------------ */
|
||||
export function inQuart(t, b, c, d) {
|
||||
return c*(t/=d)*t*t*t + b;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| outQuart:float - returns eased float value
|
||||
|
|
||||
| t:number - current time
|
||||
| b:number - beginning value
|
||||
| c:number - change in value
|
||||
| d:number - duration
|
||||
|
|
||||
| Get an eased float value based on outQuart.
|
||||
------------------------------------------ */
|
||||
export function outQuart(t, b, c, d) {
|
||||
return -c * ((t=t/d-1)*t*t*t - 1) + b;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| inOutQuart:float - returns eased float value
|
||||
|
|
||||
| t:number - current time
|
||||
| b:number - beginning value
|
||||
| c:number - change in value
|
||||
| d:number - duration
|
||||
|
|
||||
| Get an eased float value based on inOutQuart.
|
||||
------------------------------------------ */
|
||||
export function inOutQuart(t, b, c, d) {
|
||||
if ((t/=d/2) < 1) return c/2*t*t*t*t + b;
|
||||
return -c/2 * ((t-=2)*t*t*t - 2) + b;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| inQuint:float - returns eased float value
|
||||
|
|
||||
| t:number - current time
|
||||
| b:number - beginning value
|
||||
| c:number - change in value
|
||||
| d:number - duration
|
||||
|
|
||||
| Get an eased float value based on inQuint.
|
||||
------------------------------------------ */
|
||||
export function inQuint(t, b, c, d) {
|
||||
return c*(t/=d)*t*t*t*t + b;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| outQuint:float - returns eased float value
|
||||
|
|
||||
| t:number - current time
|
||||
| b:number - beginning value
|
||||
| c:number - change in value
|
||||
| d:number - duration
|
||||
|
|
||||
| Get an eased float value based on outQuint.
|
||||
------------------------------------------ */
|
||||
export function outQuint(t, b, c, d) {
|
||||
return c*((t=t/d-1)*t*t*t*t + 1) + b;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| inOutQuint:float - returns eased float value
|
||||
|
|
||||
| t:number - current time
|
||||
| b:number - beginning value
|
||||
| c:number - change in value
|
||||
| d:number - duration
|
||||
|
|
||||
| Get an eased float value based on inOutQuint.
|
||||
------------------------------------------ */
|
||||
export function inOutQuint(t, b, c, d) {
|
||||
if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b;
|
||||
return c/2*((t-=2)*t*t*t*t + 2) + b;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| inSine:float - returns eased float value
|
||||
|
|
||||
| t:number - current time
|
||||
| b:number - beginning value
|
||||
| c:number - change in value
|
||||
| d:number - duration
|
||||
|
|
||||
| Get an eased float value based on inSine.
|
||||
------------------------------------------ */
|
||||
export function inSine(t, b, c, d) {
|
||||
return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| outSine:float - returns eased float value
|
||||
|
|
||||
| t:number - current time
|
||||
| b:number - beginning value
|
||||
| c:number - change in value
|
||||
| d:number - duration
|
||||
|
|
||||
| Get an eased float value based on outSine.
|
||||
------------------------------------------ */
|
||||
export function outSine(t, b, c, d) {
|
||||
return c * Math.sin(t/d * (Math.PI/2)) + b;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| inOutSine:float - returns eased float value
|
||||
|
|
||||
| t:number - current time
|
||||
| b:number - beginning value
|
||||
| c:number - change in value
|
||||
| d:number - duration
|
||||
|
|
||||
| Get an eased float value based on inOutSine.
|
||||
------------------------------------------ */
|
||||
export function inOutSine(t, b, c, d) {
|
||||
return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| inExpo:float - returns eased float value
|
||||
|
|
||||
| t:number - current time
|
||||
| b:number - beginning value
|
||||
| c:number - change in value
|
||||
| d:number - duration
|
||||
|
|
||||
| Get an eased float value based on inExpo.
|
||||
------------------------------------------ */
|
||||
export function inExpo(t, b, c, d) {
|
||||
return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| outExpo:float - returns eased float value
|
||||
|
|
||||
| t:number - current time
|
||||
| b:number - beginning value
|
||||
| c:number - change in value
|
||||
| d:number - duration
|
||||
|
|
||||
| Get an eased float value based on outExpo.
|
||||
------------------------------------------ */
|
||||
export function outExpo(t, b, c, d) {
|
||||
return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| inOutExpo:float - returns eased float value
|
||||
|
|
||||
| t:number - current time
|
||||
| b:number - beginning value
|
||||
| c:number - change in value
|
||||
| d:number - duration
|
||||
|
|
||||
| Get an eased float value based on inOutExpo.
|
||||
------------------------------------------ */
|
||||
export function inOutExpo(t, b, c, d) {
|
||||
if (t==0) return b;
|
||||
if (t==d) return b+c;
|
||||
if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
|
||||
return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| inCirc:float - returns eased float value
|
||||
|
|
||||
| t:number - current time
|
||||
| b:number - beginning value
|
||||
| c:number - change in value
|
||||
| d:number - duration
|
||||
|
|
||||
| Get an eased float value based on inCirc.
|
||||
------------------------------------------ */
|
||||
export function inCirc(t, b, c, d) {
|
||||
return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| outCirc:float - returns eased float value
|
||||
|
|
||||
| t:number - current time
|
||||
| b:number - beginning value
|
||||
| c:number - change in value
|
||||
| d:number - duration
|
||||
|
|
||||
| Get an eased float value based on outCirc.
|
||||
------------------------------------------ */
|
||||
export function outCirc(t, b, c, d) {
|
||||
return c * Math.sqrt(1 - (t=t/d-1)*t) + b;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| inOutCirc:float - returns eased float value
|
||||
|
|
||||
| t:number - current time
|
||||
| b:number - beginning value
|
||||
| c:number - change in value
|
||||
| d:number - duration
|
||||
|
|
||||
| Get an eased float value based on inOutCirc.
|
||||
------------------------------------------ */
|
||||
export function inOutCirc(t, b, c, d) {
|
||||
if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b;
|
||||
return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| inElastic:float - returns eased float value
|
||||
|
|
||||
| t:number - current time
|
||||
| b:number - beginning value
|
||||
| c:number - change in value
|
||||
| d:number - duration
|
||||
|
|
||||
| Get an eased float value based on inElastic.
|
||||
------------------------------------------ */
|
||||
export function inElastic(t, b, c, d) {
|
||||
let s=1.70158;let p=0;let a=c;
|
||||
if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3;
|
||||
if (a < Math.abs(c)) { a=c; let s=p/4; }
|
||||
else s = p/(2*Math.PI) * Math.asin (c/a);
|
||||
return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| outElastic:float - returns eased float value
|
||||
|
|
||||
| t:number - current time
|
||||
| b:number - beginning value
|
||||
| c:number - change in value
|
||||
| d:number - duration
|
||||
|
|
||||
| Get an eased float value based on outElastic.
|
||||
------------------------------------------ */
|
||||
export function outElastic(t, b, c, d) {
|
||||
let s=1.70158;let p=0;let a=c;
|
||||
if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3;
|
||||
if (a < Math.abs(c)) { a=c; let s=p/4; }
|
||||
else s = p/(2*Math.PI) * Math.asin (c/a);
|
||||
return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| inOutElastic:float - returns eased float value
|
||||
|
|
||||
| t:number - current time
|
||||
| b:number - beginning value
|
||||
| c:number - change in value
|
||||
| d:number - duration
|
||||
|
|
||||
| Get an eased float value based on inOutElastic.
|
||||
------------------------------------------ */
|
||||
export function inOutElastic(t, b, c, d) {
|
||||
let s=1.70158;let p=0;let a=c;
|
||||
if (t==0) return b; if ((t/=d/2)==2) return b+c; if (!p) p=d*(.3*1.5);
|
||||
if (a < Math.abs(c)) { a=c; let s=p/4; }
|
||||
else s = p/(2*Math.PI) * Math.asin (c/a);
|
||||
if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
|
||||
return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| inBack:float - returns eased float value
|
||||
|
|
||||
| t:number - current time
|
||||
| b:number - beginning value
|
||||
| c:number - change in value
|
||||
| d:number - duration
|
||||
| s:number - strength
|
||||
|
|
||||
| Get an eased float value based on inBack.
|
||||
------------------------------------------ */
|
||||
export function inBack(t, b, c, d, s) {
|
||||
if (s == undefined) s = 1.70158;
|
||||
return c*(t/=d)*t*((s+1)*t - s) + b;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| outBack:float - returns eased float value
|
||||
|
|
||||
| t:number - current time
|
||||
| b:number - beginning value
|
||||
| c:number - change in value
|
||||
| d:number - duration
|
||||
| s:number - strength
|
||||
|
|
||||
| Get an eased float value based on outBack.
|
||||
------------------------------------------ */
|
||||
export function outBack(t, b, c, d, s) {
|
||||
if (s == undefined) s = 1.70158;
|
||||
return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------
|
||||
| inOutBack:float - returns eased float value
|
||||
|
|
||||
| t:number - current time
|
||||
| b:number - beginning value
|
||||
| c:number - change in value
|
||||
| d:number - duration
|
||||
| s:number - strength
|
||||
|
|
||||
| Get an eased float value based on inOutBack.
|
||||
------------------------------------------ */
|
||||
export function inOutBack(t, b, c, d, s) {
|
||||
if (s == undefined) s = 1.70158;
|
||||
if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b;
|
||||
return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b;
|
||||
}
|
7
assets/js/scroll-to/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export function scrollTo(qs: string) {
|
||||
if (process.client) {
|
||||
document.querySelector(qs)?.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
}
|
1
assets/svg/misskey_mi_bi.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg class="bi" fill="currentColor" version="1.1" viewBox="0 0 135.47 135.47" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-38.1 -100.7)"><path d="m54.63 120.96c-1.9779 0-3.8618 0.32979-5.6513 0.98909-3.2023 1.1302-5.8396 3.1553-7.9117 6.0751-1.9779 2.8256-2.9667 5.9807-2.9667 9.4656v61.88c0 4.5209 1.601 8.4294 4.8033 11.726 3.2965 3.2023 7.2055 4.8038 11.726 4.8038 4.6151 0 8.5236-1.6015 11.726-4.8038 3.2965-3.2965 4.9449-7.205 4.9449-11.726v-11.253c0.03556-2.4371 2.546-1.7977 3.8148 0 2.3763 4.1153 7.4142 7.6497 13.28 7.6295 5.8656-0.0202 10.737-2.9202 13.28-7.6295 0.96304-1.1358 3.6774-3.071 3.9558 0v11.253c0 4.5209 1.601 8.4294 4.8033 11.726 3.2965 3.2023 7.2055 4.8038 11.726 4.8038 4.6151 0 8.5236-1.6015 11.726-4.8038 3.2965-3.2965 4.9449-7.205 4.9449-11.726v-61.88c0-3.4849-1.0357-6.64-3.1078-9.4656-1.9779-2.9198-4.5683-4.9448-7.7706-6.0751-1.8837-0.6593-3.7676-0.98909-5.6513-0.98909-5.086 0-9.3712 1.9782-12.856 5.934l-16.775 19.632c-0.37674 0.28256-1.6248 2.4428-4.2757 2.4428-2.6509 0-3.7574-2.1602-4.1341-2.4428l-16.916-19.632c-3.3907-3.9558-7.6289-5.934-12.715-5.934zm104.53 0c-3.9558 0-7.3464 1.4129-10.172 4.2385-2.7314 2.7314-4.0969 6.0751-4.0969 10.031 0 3.9558 1.3655 7.3464 4.0969 10.172 2.8256 2.7314 6.2162 4.0974 10.172 4.0974 3.9558 0 7.3464-1.366 10.172-4.0974 2.8256-2.8256 4.2385-6.2162 4.2385-10.172 0-3.9558-1.4129-7.2995-4.2385-10.031-2.8256-2.8256-6.2162-4.2385-10.172-4.2385zm0.14107 31.364c-3.9558 0-7.3464 1.4129-10.172 4.2385s-4.238 6.2162-4.238 10.172v34.896c0 3.9558 1.4124 7.3464 4.238 10.172 2.8256 2.7314 6.2162 4.0974 10.172 4.0974s7.2995-1.366 10.031-4.0974c2.8256-2.8256 4.2385-6.2162 4.2385-10.172v-34.896c0-3.9558-1.4129-7.3464-4.2385-10.172-2.7314-2.8256-6.0751-4.2385-10.031-4.2385z"/></g></svg>
|
After Width: | Height: | Size: 1.7 KiB |
56
components/g/Button.vue
Normal file
@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<GNuxtLink v-if="buttonType == 'link'" :to="to" :class="[$style.button, color == 'accent' ? $style.buttonAccent : $style.buttonPlain]">
|
||||
<slot></slot>
|
||||
</GNuxtLink>
|
||||
<button v-else-if="buttonType == 'button'" @click="onClick()" :class="[$style.button, color == 'accent' ? $style.buttonAccent : $style.buttonPlain]">
|
||||
<slot></slot>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = withDefaults(defineProps<({
|
||||
/** ボタンのタイプ */
|
||||
buttonType: 'button';
|
||||
} | {
|
||||
/** ボタンのタイプ */
|
||||
buttonType: 'link';
|
||||
/** 移動先 */
|
||||
to: string;
|
||||
}) & {
|
||||
/** 色設定 */
|
||||
color: 'accent' | 'plain';
|
||||
}>(), {
|
||||
color: 'plain',
|
||||
});
|
||||
|
||||
const emits = defineEmits<{
|
||||
(e: 'click'): void;
|
||||
}>();
|
||||
|
||||
function onClick() {
|
||||
emits('click');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style module>
|
||||
.button {
|
||||
@apply py-3 px-6 text-lg font-bold rounded-full transition-all;
|
||||
box-shadow: 0 8px 20px -5px rgba(0, 0, 0, .2);
|
||||
}
|
||||
|
||||
.buttonAccent {
|
||||
@apply text-white;
|
||||
box-shadow: 0 8px 20px -5px rgba(134, 179, 0, .4);
|
||||
background-image: linear-gradient(90deg, #86b300, #4ab300, #4ab300);
|
||||
background-size: 200% 100%;
|
||||
background-position-x: 0%;
|
||||
}
|
||||
|
||||
.buttonAccent:hover {
|
||||
background-position-x: 100%;
|
||||
}
|
||||
|
||||
.buttonPlain {
|
||||
@apply text-accent-600 bg-white hover:text-accent-700 hover:bg-lime-100;
|
||||
}
|
||||
</style>
|
21
components/g/Dots.vue
Normal file
@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<svg fill="none">
|
||||
<defs>
|
||||
<pattern :id="id" x="0" y="0" :width="space" :height="space" patternUnits="userSpaceOnUse">
|
||||
<circle cx="2" cy="2" r="2" fill="currentColor"></circle>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" :fill="`url(#${id})`"></rect>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{
|
||||
space?: number;
|
||||
}>(), {
|
||||
space: 15,
|
||||
});
|
||||
|
||||
const idchars = 'abcdefghijklmnopqrstuvwxyz';
|
||||
const id = Array.from(Array(32)).map(() => idchars[Math.floor(Math.random() * idchars.length)]).join('');
|
||||
</script>
|
@ -1,11 +1,13 @@
|
||||
<template>
|
||||
<div>
|
||||
<nav class="h-[4rem] fixed top-0 left-0 w-full bg-white dark:bg-gray-700 bg-opacity-80 backdrop-blur-lg z-[9950]">
|
||||
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
defineProps<{
|
||||
isOpen: boolean;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
6
components/g/NuxtLink.ts
Normal file
@ -0,0 +1,6 @@
|
||||
// 末尾スラッシュの統一のためにラッパーNuxtLinkを実装
|
||||
|
||||
export default defineNuxtLink({
|
||||
componentName: "NuxtLink",
|
||||
trailingSlash: 'append',
|
||||
});
|
20
components/index/Decenterized.vue
Normal file
@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<div class="bg-white dark:bg-slate-950 rounded-2xl p-6 lg:p-12 lg:py-24">
|
||||
<I18nT keypath="_landing._decenterized.title" tag="h2" class="text-center text-xl lg:text-3xl font-bold font-title mb-6">
|
||||
<span class="em">{{ $t('_landing._decenterized.decenterizedPlatform') }}</span>
|
||||
</I18nT>
|
||||
<I18nT keypath="_landing._decenterized.description" tag="p" class="lg:text-lg leading-relaxed lg:leading-loose">
|
||||
<span class="font-bold em">{{ $t('_landing._decenterized.activityPub') }}</span>
|
||||
</I18nT>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.em {
|
||||
background: linear-gradient(transparent 70%,rgba(191,255,0,.5019607843) 0%);
|
||||
}
|
||||
</style>
|
12
components/index/Donation.vue
Normal file
@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<div class="bg-white dark:bg-slate-950 rounded-2xl p-6 lg:p-12 lg:py-24 mx-auto max-w-screen-lg">
|
||||
<h2 class="text-center text-xl lg:text-3xl font-bold font-title mb-6">{{ $t('_landing._donation.title') }}</h2>
|
||||
<p class="lg:text-lg leading-relaxed lg:leading-loose mb-6">{{ $t('_landing._donation.description') }}</p>
|
||||
<p class="text-center">
|
||||
<GButton button-type="link" :to="localePath('/docs/donate/')" color="accent">{{ $t('learnMore') }}</GButton>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
const localePath = useLocalePath();
|
||||
</script>
|
93
components/index/Features.vue
Normal file
@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<div class="grid features gap-12">
|
||||
<div>
|
||||
<div class="sticky top-12">
|
||||
<h2 class="font-title font-bold text-2xl lg:text-4xl mb-6">{{ $t('_landing._features._root.title') }}</h2>
|
||||
<p class="text-lg">{{ $t('_landing._features._root.description') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-8">
|
||||
<div v-fade-in class="item"><div class="content">
|
||||
<img src="/img/top-features/top-features-note.png" class="img" alt="some notes">
|
||||
<h3 class="title">{{ $t('_landing._features._note.title') }}</h3>
|
||||
<div class="description">{{ $t('_landing._features._note.description') }}</div>
|
||||
</div></div>
|
||||
<div v-fade-in class="item"><div class="content">
|
||||
<img src="/img/top-features/top-features-reaction.png" class="img" alt="some emojis in the reaction picker">
|
||||
<h3 class="title">{{ $t('_landing._features._reaction.title') }}</h3>
|
||||
<div class="description">{{ $t('_landing._features._reaction.description') }}</div>
|
||||
</div></div>
|
||||
<div v-fade-in class="item"><div class="content">
|
||||
<img src="/img/top-features/top-features-theme.png" class="img" alt="color palette">
|
||||
<h3 class="title">{{ $t('_landing._features._theme.title') }}</h3>
|
||||
<div class="description">{{ $t('_landing._features._theme.description') }}</div>
|
||||
</div></div>
|
||||
<div v-fade-in class="item"><div class="content">
|
||||
<img src="/img/top-features/top-features-charts.png" class="img" alt="Charts">
|
||||
<h3 class="title">{{ $t('_landing._features._charts.title') }}</h3>
|
||||
<div class="description">{{ $t('_landing._features._charts.description') }}</div>
|
||||
</div></div>
|
||||
</div>
|
||||
<div class="space-y-8 lg:pt-24">
|
||||
<div v-fade-in class="item"><div class="content">
|
||||
<img src="/img/top-features/top-features-federation.png" class="img" alt="logos of Misskey and other ActivityPub server software">
|
||||
<h3 class="title">{{ $t('_landing._features._federation.title') }}</h3>
|
||||
<div class="description">{{ $t('_landing._features._federation.description') }}</div>
|
||||
</div></div>
|
||||
<div v-fade-in class="item"><div class="content">
|
||||
<img src="/img/top-features/top-features-drive.png" class="img" alt="a list of files in Misskey Drive">
|
||||
<h3 class="title">{{ $t('_landing._features._drive.title') }}</h3>
|
||||
<div class="description">{{ $t('_landing._features._drive.description') }}</div>
|
||||
</div></div>
|
||||
<div v-fade-in class="item"><div class="content">
|
||||
<img src="/img/top-features/top-features-thread.png" class="img" alt="multiple messages arranged in a thread">
|
||||
<h3 class="title">{{ $t('_landing._features._thread.title') }}</h3>
|
||||
<div class="description">{{ $t('_landing._features._thread.description') }}</div>
|
||||
</div></div>
|
||||
<div v-fade-in class="item"><div class="content">
|
||||
<img src="/img/top-features/top-features-widgets.png" class="img" alt="sample widget showing activity as colored dots">
|
||||
<h3 class="title">{{ $t('_landing._features._widgets.title') }}</h3>
|
||||
<div class="description">{{ $t('_landing._features._widgets.description') }}</div>
|
||||
</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { vFadeIn } from '@/assets/js/fadein';
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.features {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@screen lg {
|
||||
.features {
|
||||
grid-template-columns: 300px repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.item > .content {
|
||||
@apply p-5 bg-white dark:bg-slate-700 rounded-xl;
|
||||
}
|
||||
|
||||
.item .img {
|
||||
@apply w-full aspect-[2/1] object-cover rounded-lg mb-4;
|
||||
}
|
||||
|
||||
.item .title {
|
||||
@apply font-title text-xl lg:text-2xl font-bold mb-2;
|
||||
}
|
||||
|
||||
.__v_fadeIn_out > * {
|
||||
opacity: 0;
|
||||
transform: scale(0.9) translateY(-50px);
|
||||
}
|
||||
|
||||
.__v_fadeIn_in > * {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
|
||||
</style>
|
63
components/index/GetStarted.vue
Normal file
@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2 class="mb-12 text-2xl lg:text-3xl text-center font-bold font-title">{{ $t('_landing._getStarted.title') }}</h2>
|
||||
<div class="grid gap-12 lg:grid-cols-3">
|
||||
<div v-fade-in class="find">
|
||||
<div class="item">
|
||||
<div class="icon"><img src="/img/emojis/ringed-planet_1fa90.png" aria-hidden="true"></div>
|
||||
<h3 class="title">{{ $t('_landing._getStarted._find.title') }}</h3>
|
||||
<div class="description"></div>
|
||||
<GNuxtLink class="link" :to="localePath('/servers/')">{{ $t('_landing._getStarted._find.list') }}</GNuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
<div v-fade-in class="create">
|
||||
<div class="item">
|
||||
<div class="icon"><img src="/img/emojis/package_1f4e6.png" aria-hidden="true"></div>
|
||||
<h3 class="title">{{ $t('_landing._getStarted._create.title') }}</h3>
|
||||
<div class="description"></div>
|
||||
<GNuxtLink class="link" to="./docs/install.html">{{ $t('_landing._getStarted._create.guide') }}</GNuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
<div v-fade-in class="docs">
|
||||
<div class="item">
|
||||
<div class="icon"><img src="/img/emojis/light-bulb_1f4a1.png" aria-hidden="true"></div>
|
||||
<h3 class="title">{{ $t('_landing._getStarted._docs.title') }}</h3>
|
||||
<div class="description"></div>
|
||||
<GNuxtLink class="link" to="./docs/misskey.html">{{ $t('_landing._getStarted._docs.docs') }}</GNuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { vFadeIn } from 'assets/js/fadein';
|
||||
|
||||
const localePath = useLocalePath();
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.item {
|
||||
@apply rounded-2xl py-12 px-4 text-center bg-white dark:bg-slate-700;
|
||||
}
|
||||
|
||||
.find > .item {
|
||||
@apply bg-accent-600 bg-opacity-60 backdrop-blur-lg text-white;
|
||||
}
|
||||
|
||||
.item > .icon {
|
||||
@apply w-12 mb-4 mx-auto;
|
||||
}
|
||||
|
||||
.item > .title {
|
||||
@apply font-title font-bold text-xl text-center mb-6;
|
||||
}
|
||||
|
||||
.item > .link {
|
||||
@apply px-6 py-3 rounded-lg bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors font-bold text-lg;
|
||||
}
|
||||
|
||||
.find .link {
|
||||
@apply text-accent-600 bg-white hover:bg-accent-100;
|
||||
}
|
||||
</style>
|
@ -1,23 +0,0 @@
|
||||
<template>
|
||||
<div class="container mx-auto">
|
||||
<MkLogo />
|
||||
<div class="tagline" id="tagline">
|
||||
<div class="row">Interplanetary</div>
|
||||
<div class="row">microblogging</div>
|
||||
<div class="row">platform.🚀</div>
|
||||
</div>
|
||||
<div class="description">{{ $t('_landing._hero.description') }}</div>
|
||||
<div class="buttons">
|
||||
<a class="start" href="#gettingStarted">{{ $t('_landing._hero.gettingStarted') }}</a>
|
||||
<a class="more" href="#learnMore">{{ $t('_landing._hero.learnMore') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import MkLogo from '@/assets/svg/misskey-logotype.svg';
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
47
components/index/KeyFeatures.vue
Normal file
@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<div class="backdrop-blur-lg bg-white dark:bg-slate-900 bg-opacity-50 dark:bg-opacity-50 rounded-2xl p-6 lg:p-12 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<div class="item">
|
||||
<div class="icon"><img src="/img/emojis/four-leaf-clover_1f340.png" aria-hidden="true"></div>
|
||||
<h3 class="title">{{ $t('_landing._keyFeatures._open.title') }}</h3>
|
||||
<div class="description">{{ $t('_landing._keyFeatures._open.description') }}</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="icon"><img src="/img/emojis/ringed-planet_1fa90.png" aria-hidden="true"></div>
|
||||
<h3 class="title">{{ $t('_landing._keyFeatures._federated.title') }}</h3>
|
||||
<div class="description">{{ $t('_landing._keyFeatures._federated.description') }}</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="icon"><img src="/img/emojis/package_1f4e6.png" aria-hidden="true"></div>
|
||||
<h3 class="title">{{ $t('_landing._keyFeatures._multifunction.title') }}</h3>
|
||||
<div class="description">{{ $t('_landing._keyFeatures._multifunction.description') }}</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="icon"><img src="/img/emojis/gear_2699-fe0f.png" aria-hidden="true"></div>
|
||||
<h3 class="title">{{ $t('_landing._keyFeatures._customizable.title') }}</h3>
|
||||
<div class="description">{{ $t('_landing._keyFeatures._customizable.description') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.item {
|
||||
@apply space-y-2;
|
||||
}
|
||||
|
||||
.title {
|
||||
@apply font-title font-bold text-center text-xl md:text-2xl;
|
||||
}
|
||||
|
||||
.icon {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.icon img {
|
||||
width: 70px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
56
components/index/Nav.vue
Normal file
@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<nav class="sticky top-0 z-[9900] md:relative container mx-auto max-w-screen-xl h-16 lg:h-20 grid items-center grid-cols-2 md:grid-cols-4 lg:grid-cols-6 p-4 bg-white bg-opacity-80 lg:bg-transparent">
|
||||
<div class="">
|
||||
<GNuxtLink to="/" class="flex items-center space-x-2 hover:opacity-80">
|
||||
<MiIcon class="h-8 w-8" />
|
||||
<div class="font-title font-bold text-lg">Misskey Hub</div>
|
||||
</GNuxtLink>
|
||||
</div>
|
||||
<ul class="hidden lg:col-span-4 lg:space-x-8 xl:space-x-10 lg:flex justify-center">
|
||||
<li v-for="item in NavData.center">
|
||||
<GNuxtLink :to="localePath(item.to)" class="rounded-full px-4 py-1.5 hover:bg-slate-300 hover:bg-opacity-50 bg-blend-multiply">
|
||||
<component v-if="item.icon" :is="item.icon" class="h-5 w-5" />
|
||||
<template v-else>
|
||||
{{ $t(item.i18n) }}
|
||||
</template>
|
||||
</GNuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
<div>
|
||||
<ul class="hidden lg:col-span-4 lg:space-x-4 lg:flex justify-center">
|
||||
<li class="relative group">
|
||||
<a class="text-white hover:opacity-80" href="#"><I18nIcon class="h-5 w-5" /><span class="sr-only">{{ $t('_nav.switchLang') }}</span></a>
|
||||
<div class="absolute top-6 right-0 hidden group-hover:block z-[9955]">
|
||||
<ul class="px-4 py-2 bg-slate-50 dark:bg-slate-700 rounded-lg shadow-lg space-y-1">
|
||||
<li v-for="locale in locales">
|
||||
<GNuxtLink :to="switchLocalePath(locale.code)" class="hover:text-accent-600">
|
||||
{{ locale.name }}
|
||||
</GNuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<li class="border-l"></li>
|
||||
<li v-for="item in NavData.right" class="text-white">
|
||||
<GNuxtLink :to="item.to" class="hover:opacity-80">
|
||||
<component v-if="item.icon" :is="item.icon" class="h-5 w-5" />
|
||||
<template v-else>
|
||||
{{ $t(item.i18n) }}
|
||||
</template>
|
||||
</GNuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import MiIcon from '@/assets/svg/misskey_mi_bi.svg';
|
||||
import I18nIcon from 'bi/translate.svg';
|
||||
import NavData from '@/assets/data/nav';
|
||||
|
||||
const { locales } = useI18n();
|
||||
const switchLocalePath = useSwitchLocalePath();
|
||||
const localePath = useLocalePath();
|
||||
</script>
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="absolute top-0 left-0 w-full h-full overflow-hidden">
|
||||
<div class="absolute z-0 top-0 left-0 w-full h-full overflow-hidden">
|
||||
<div v-parallax="1.2" class="blobs object1"><Blob1 aria-hidden="true" /></div>
|
||||
<div v-parallax="1.2" class="blobs object2"><Blob2 aria-hidden="true" /></div>
|
||||
<div v-parallax="1.2" class="blobs object3"><Blob2 aria-hidden="true" /></div>
|
||||
@ -7,20 +7,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Ref } from 'vue';
|
||||
import { vParallax } from '@/assets/js/parallax';
|
||||
|
||||
import Blob1 from '@/assets/svg/top-bg-object1.svg';
|
||||
import Blob2 from '@/assets/svg/top-bg-object2.svg';
|
||||
|
||||
const vParallax = {
|
||||
mounted: (src: HTMLElement, binding: Ref<number>) => {
|
||||
src.style.willChange = 'transform';
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
src.style.transform = `translateY(${window.scrollY / binding.value}px)`;
|
||||
}, { passive: true });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@ -46,6 +37,7 @@ const vParallax = {
|
||||
left: -250px;
|
||||
top: 500px;
|
||||
width: 1000px;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.object2 > svg {
|
||||
@ -56,6 +48,7 @@ const vParallax = {
|
||||
right: -300px;
|
||||
top: 1400px;
|
||||
width: 1000px;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.object3 > svg {
|
||||
@ -67,3 +60,4 @@ const vParallax = {
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
assets/js/paralax
|
42
components/index/hero/Left.vue
Normal file
@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div class="relative space-y-6">
|
||||
<MkLogo class="block mx-auto lg:ml-0 w-full max-w-[250px]" />
|
||||
<h2 ref="tagline" class="text-center font-title lg:text-start font-bold tracking-wide text-3xl sm:text-5xl md:text-6xl leading-relaxed sm:leading-relaxed md:leading-relaxed" id="tagline">
|
||||
<div class="row">Interplanetary</div>
|
||||
<div class="row">microblogging</div>
|
||||
<div class="row">platform.🚀</div>
|
||||
</h2>
|
||||
<div class="max-w-lg mx-auto lg:mx-0 text-lg text-center lg:text-start">{{ $t('_landing._hero.description') }}</div>
|
||||
<div class="w-fit space-x-4 mx-auto lg:mx-0">
|
||||
<GButton button-type="button" color="accent" @click="scrollTo('#getStarted')">{{ $t('_landing._hero.gettingStarted') }}</GButton>
|
||||
<GButton button-type="button" @click="scrollTo('#learnMore')">{{ $t('learnMore') }}</GButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import MkLogo from '@/assets/svg/misskey-logotype.svg';
|
||||
import { scrollTo } from '@/assets/js/scroll-to';
|
||||
|
||||
const tagline = ref<HTMLElement>();
|
||||
|
||||
onMounted(() => {
|
||||
window.setTimeout(() => {
|
||||
if (tagline.value) {
|
||||
for (let i = 0; i < tagline.value.children.length; i++) {
|
||||
const row = tagline.value.children[i];
|
||||
window.setTimeout(() => { row.classList.add('shown'); }, 200 * i);
|
||||
}
|
||||
}
|
||||
}, 250);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.row {
|
||||
@apply opacity-0 translate-x-24 transition duration-1000
|
||||
}
|
||||
.row.shown {
|
||||
@apply opacity-100 translate-x-0;
|
||||
}
|
||||
</style>
|
61
components/index/hero/Particles.vue
Normal file
@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<ClientOnly>
|
||||
<transition name="fade">
|
||||
<div v-if="particleEnabled" ref="container" class="absolute top-0 left-0 w-full h-[800px] select-none pointer-events-none" :style="colorVars"></div>
|
||||
</transition>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Loader } from '@/assets/js/particles/loader';
|
||||
import tailwindConfig from 'tailwindcss/resolveConfig';
|
||||
|
||||
const colorMode = useColorMode();
|
||||
const container = ref<HTMLElement>();
|
||||
const isMounted = ref<boolean>(false);
|
||||
const particleEnabled = ref<boolean>(true);
|
||||
|
||||
const colorVars = computed<string>(() => {
|
||||
const out = [`--c-brand: ${tailwindConfig.theme?.extend.colors.accent['600'] || '#86b300'}`]
|
||||
if (colorMode.preference == 'dark') {
|
||||
out.join(`--c-text: `)
|
||||
}
|
||||
return out.join(' ');
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
isMounted.value = true;
|
||||
});
|
||||
|
||||
watch(container, (to) => {
|
||||
if (isMounted.value && process.client && to) {
|
||||
const loader = new Loader(to);
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
particleEnabled.value = false;
|
||||
}, {
|
||||
passive: true,
|
||||
once: true,
|
||||
});
|
||||
|
||||
|
||||
onUnmounted(() => {
|
||||
setTimeout(() => {
|
||||
loader.destroy();
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 1s ease;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
105
components/index/hero/Right.vue
Normal file
@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<div class="absolute top-0 w-full hidden lg:block">
|
||||
<GDots v-parallax="1.4" class="dots dots1" :space="30"/>
|
||||
<GDots v-parallax="1.5" class="dots dots2" :space="30"/>
|
||||
<img v-parallax="2" src="/img/hero/screenshot-desktop.png" class="screenshot desktop" alt="screenshot of Misskey in a PC browser">
|
||||
<img v-parallax="3" src="/img/hero/screenshot-mobile.png" class="screenshot mobile" alt="screenshot of Misskey in a mobile browser">
|
||||
<img v-parallax="4" src="/img/hero/ai.png" class="ai" alt="Ai-chan, Misskey's mascott">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { vParallax } from '@/assets/js/parallax';
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dots {
|
||||
@apply absolute text-accent-600 pointer-events-none select-none w-[300px] h-[300px];
|
||||
}
|
||||
|
||||
.dots1 {
|
||||
right: 900px;
|
||||
top: 200px;
|
||||
}
|
||||
|
||||
.dots2 {
|
||||
right: 120px;
|
||||
top: 500px;
|
||||
}
|
||||
|
||||
.screenshot {
|
||||
@apply absolute rounded-lg shadow-lg select-none pointer-events-none;
|
||||
}
|
||||
|
||||
.screenshot.mobile {
|
||||
right: 650px;
|
||||
top: 400px;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.screenshot.desktop {
|
||||
width: 800px;
|
||||
top: 128px;
|
||||
right: 300px;
|
||||
}
|
||||
|
||||
.ai {
|
||||
@apply absolute select-none pointer-events-none;
|
||||
right: 130px;
|
||||
top: 128px;
|
||||
height: 900px;
|
||||
}
|
||||
|
||||
@media (max-width: 1800px) {
|
||||
.dots1 {
|
||||
right:800px
|
||||
}
|
||||
|
||||
.screenshot.desktop {
|
||||
width: 700px
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1700px) {
|
||||
.dots1 {
|
||||
right:700px
|
||||
}
|
||||
|
||||
.screenshot.desktop {
|
||||
width: 600px
|
||||
}
|
||||
|
||||
.screenshot.mobile {
|
||||
height: 350px;
|
||||
right: 500px
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1600px) {
|
||||
.dots1 {
|
||||
right:600px
|
||||
}
|
||||
|
||||
.screenshot.desktop {
|
||||
width: 500px
|
||||
}
|
||||
|
||||
.screenshot.mobile {
|
||||
height: 300px
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1500px) {
|
||||
.dots1 {
|
||||
right:600px
|
||||
}
|
||||
|
||||
.screenshot.desktop {
|
||||
right: 250px
|
||||
}
|
||||
|
||||
.screenshot.mobile {
|
||||
right: 450px
|
||||
}
|
||||
}
|
||||
</style>
|
@ -4,7 +4,7 @@ const isNavOpen = ref<boolean>(false);
|
||||
<template>
|
||||
<div>
|
||||
<GNav @toggleNav="isNavOpen = !isNavOpen" :is-open="isNavOpen" />
|
||||
<div class="min-h-screen main-content overflow-x-hidden">
|
||||
<div class="main-content overflow-x-hidden">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<GFooter />
|
||||
|
11
layouts/landing.vue
Normal file
@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
const isNavOpen = ref<boolean>(false);
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<div class="main-content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<GFooter />
|
||||
</div>
|
||||
</template>
|
@ -1,10 +1,75 @@
|
||||
noScript: "現在Javascriptが無効になっています。サイトの表示にはJavascriptが必須となりますので有効にしてください。"
|
||||
learnMore: "詳しく知る"
|
||||
_seo:
|
||||
siteName: "Misskey Hub"
|
||||
defaultTitleTagline: "ノートでひろがるネットワーク"
|
||||
defaultDescription: "Misskeyはオープンソースの分散型ソーシャルネットワーキングプラットフォームです。"
|
||||
_nav:
|
||||
servers: "サーバー"
|
||||
docs: "ドキュメント"
|
||||
blog: "ブログ"
|
||||
switchLang: "言語の設定"
|
||||
_landing:
|
||||
_hero:
|
||||
description: "Misskeyはオープンソースの分散型ソーシャルネットワーキングプラットフォームです。"
|
||||
gettingStarted: "はじめる"
|
||||
learnMore: "詳しく知る"
|
||||
_keyFeatures:
|
||||
_open:
|
||||
title: "オープン"
|
||||
description: "Misskeyはオープンソースのソフトウェアで、誰でも自由にMisskeyを利用できます。"
|
||||
_federated:
|
||||
title: "分散"
|
||||
description: "Misskeyは分散型のプロトコルを実装しているため、異なるサーバーのコミュニティ同士が繋がります。"
|
||||
_multifunction:
|
||||
title: "高機能"
|
||||
description: "Misskeyは他にはない様々な機能を備えていて、プロのようにmicro-blogging可能です。"
|
||||
_customizable:
|
||||
title: "高カスタマイズ性"
|
||||
description: "Misskeyの柔軟なWebインターフェイスにより、自分好みのUIを作れます。"
|
||||
_decenterized:
|
||||
title: "Misskeyは{0}"
|
||||
decenterizedPlatform: "分散型プラットフォーム"
|
||||
description: "Misskeyはフリーかつオープンなプロジェクトで、誰でも自由にMisskeyを使ったサーバーを作成できるため、既に様々なサーバーがインターネット上に公開されています。また重要な特徴として、Misskeyは{0}と呼ばれる分散通信プロトコルを実装しているので、どのサーバーを選んでも他のサーバーのユーザーとやりとりすることができます。これが分散型と言われる所以で、単一の運営者によって単一のURLで公開されるような、Twitterなどの他サービスとは根本的に異なっています。サーバーによって主な話題のテーマやユーザー層、言語などは異なり、自分にあったサーバーを探すのも楽しみのひとつです(もちろん自分のサーバーを作るのも一興です)。"
|
||||
activityPub: "ActivityPub"
|
||||
_features:
|
||||
_root:
|
||||
title: "主な機能"
|
||||
description: "Misskeyは一般的なものから特別なものまで、様々な機能を持っています。その一部を紹介します。"
|
||||
_note:
|
||||
title: "ノート"
|
||||
description: "Misskeyでは、ユーザーの投稿は「ノート」と呼ばれます。他のノートを引用したり、画像、動画、オーディオ、その他の任意のファイルを添付することもできます。"
|
||||
_federation:
|
||||
title: "連合"
|
||||
description: "オープンな分散プロトコルであるActivityPubを実装しているため、他のMisskeyサーバーだけでなく、ActivityPubをサポートする他のソフトウェアともやりとりできます。"
|
||||
_reaction:
|
||||
title: "リアクション"
|
||||
description: "ノートには「リアクション」を付けることができ、簡単・気軽に自分のフィーリングを表現して伝えることが出来ます。"
|
||||
_drive:
|
||||
title: "ドライブ"
|
||||
description: "アップロードしたファイルを管理するインターフェイスがあります。そのため、お気に入りの画像をフォルダにまとめたり、再度共有することも簡単に行えます。"
|
||||
_theme:
|
||||
title: "テーマ"
|
||||
description: "自分の好きなデザインでMisskeyを使えます。もちろんダークモードも完全サポート。自分で高度にテーマを作ることも可能です。"
|
||||
_thread:
|
||||
title: "スレッド"
|
||||
description: "もちろんノートはスレッドにすることができ、気が済むまで会話を続けられます。"
|
||||
_charts:
|
||||
title: "チャート"
|
||||
description: "Misskeyは組み込みのチャートエンジンを備えていて、サーバーの利用状況などが簡単に可視化できます。"
|
||||
_widgets:
|
||||
title: "ウィジェット"
|
||||
description: "様々な種類のウィジェットを配置し、UIを好みにカスタマイズできます。"
|
||||
_getStarted:
|
||||
title: "Misskeyをはじめよう"
|
||||
_find:
|
||||
title: "サーバーを見つける"
|
||||
list: "サーバーのリスト"
|
||||
_create:
|
||||
title: "サーバーを作成"
|
||||
guide: "セットアップガイド"
|
||||
_docs:
|
||||
title: "さらに詳しく知る"
|
||||
docs: "ドキュメントを見る"
|
||||
_donation:
|
||||
title: "寄付のお願い"
|
||||
description: "Misskeyは非営利なため、開発資金は皆様からの寄付に頼っています。Misskeyを気に入られたら、今後も開発を続けられるようにぜひ支援をお願いします。"
|
@ -11,13 +11,14 @@ export default defineNuxtConfig({
|
||||
modules: [
|
||||
'@nuxt/content',
|
||||
'@nuxtjs/i18n',
|
||||
'@nuxtjs/color-mode',
|
||||
],
|
||||
app: {
|
||||
head: {
|
||||
link: [
|
||||
{ rel: 'preconnect', href: 'https://fonts.googleapis.com' },
|
||||
{ rel: 'preconnect', href: 'https://fonts.gstatic.com' },
|
||||
{ rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Nunito:wght@400;700&display=swap' },
|
||||
{ rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Capriola&family=Nunito:ital,wght@0,400;0,700;1,400;1,700&display=swap' },
|
||||
{ rel: 'stylesheet', href: '/fonts/fonts.css' },
|
||||
],
|
||||
},
|
||||
@ -25,12 +26,19 @@ export default defineNuxtConfig({
|
||||
i18n: {
|
||||
vueI18n: './i18n.config.ts',
|
||||
locales: [
|
||||
{ code: 'ja', iso: 'ja-JP' },
|
||||
{ code: 'en', iso: 'en-US' },
|
||||
{ code: 'ja', iso: 'ja-JP', name: '日本語' },
|
||||
{ code: 'en', iso: 'en-US', name: 'English' },
|
||||
{ code: 'ko', iso: 'ko-KR', name: '한국어' },
|
||||
{ code: 'it', iso: 'it-IT', name: 'Italiano' },
|
||||
{ code: 'pl', iso: 'pl-PL', name: 'Polski' },
|
||||
{ code: 'fr', iso: 'fr-FR', name: 'Français' }
|
||||
],
|
||||
defaultLocale: 'ja',
|
||||
strategy: 'prefix',
|
||||
},
|
||||
colorMode: {
|
||||
classSuffix: '',
|
||||
},
|
||||
postcss: {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
|
@ -12,16 +12,22 @@
|
||||
"@modyfi/vite-plugin-yaml": "^1.0.4",
|
||||
"@nuxt/content": "^2.7.0",
|
||||
"@nuxt/devtools": "latest",
|
||||
"@nuxtjs/color-mode": "^3.3.0",
|
||||
"@nuxtjs/i18n": "8.0.0-beta.13",
|
||||
"@types/node": "^18",
|
||||
"@types/three": "^0.153.0",
|
||||
"@types/tinycolor2": "^1.4.3",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"bootstrap-icons": "^1.10.5",
|
||||
"github-markdown-css": "^5.2.0",
|
||||
"meshline": "^3.1.6",
|
||||
"nuxt": "^3.6.2",
|
||||
"postcss": "^8.4.25",
|
||||
"schema-dts": "^1.1.2",
|
||||
"sitemap": "^7.1.1",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"three": "^0.154.0",
|
||||
"tinycolor2": "^1.6.0",
|
||||
"vite-svg-loader": "^4.0.0"
|
||||
},
|
||||
"packageManager": "pnpm@8.6.0"
|
||||
|
@ -1,12 +1,29 @@
|
||||
<template>
|
||||
<div class="relative bg-gray-50 min-h-screen">
|
||||
<IndexBg />
|
||||
<IndexHero />
|
||||
<div class="relative min-h-full">
|
||||
<IndexHeroBg />
|
||||
<IndexHeroParticles />
|
||||
<IndexNav />
|
||||
<IndexHeroRight />
|
||||
<div class="relative container mx-auto p-6 md:p-8 max-w-screen-sm lg:max-w-screen-xl">
|
||||
<IndexHeroLeft />
|
||||
</div>
|
||||
<main class="relative container mx-auto max-w-screen-xl px-6 mt-32 space-y-16">
|
||||
<IndexKeyFeatures id="learnMore" />
|
||||
<IndexDecenterized />
|
||||
<GDots class="w-[95%] mx-auto text-accent-600" :space="30" />
|
||||
<IndexFeatures />
|
||||
<GDots class="w-[95%] mx-auto text-accent-600" :space="30" />
|
||||
<IndexGetStarted id="getStarted" />
|
||||
<GDots class="w-[95%] mx-auto text-accent-600" :space="30" />
|
||||
<IndexDonation />
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
definePageMeta({
|
||||
layout: 'landing',
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
13
pages/servers.vue
Normal file
@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -14,12 +14,21 @@ devDependencies:
|
||||
'@nuxt/devtools':
|
||||
specifier: latest
|
||||
version: 0.6.7(nuxt@3.6.2)(vite@4.4.2)
|
||||
'@nuxtjs/color-mode':
|
||||
specifier: ^3.3.0
|
||||
version: 3.3.0
|
||||
'@nuxtjs/i18n':
|
||||
specifier: 8.0.0-beta.13
|
||||
version: 8.0.0-beta.13(vue@3.3.4)
|
||||
'@types/node':
|
||||
specifier: ^18
|
||||
version: 18.0.0
|
||||
'@types/three':
|
||||
specifier: ^0.153.0
|
||||
version: 0.153.0
|
||||
'@types/tinycolor2':
|
||||
specifier: ^1.4.3
|
||||
version: 1.4.3
|
||||
autoprefixer:
|
||||
specifier: ^10.4.14
|
||||
version: 10.4.14(postcss@8.4.25)
|
||||
@ -29,6 +38,9 @@ devDependencies:
|
||||
github-markdown-css:
|
||||
specifier: ^5.2.0
|
||||
version: 5.2.0
|
||||
meshline:
|
||||
specifier: ^3.1.6
|
||||
version: 3.1.6(three@0.154.0)
|
||||
nuxt:
|
||||
specifier: ^3.6.2
|
||||
version: 3.6.2(@types/node@18.0.0)(typescript@5.1.6)
|
||||
@ -44,6 +56,12 @@ devDependencies:
|
||||
tailwindcss:
|
||||
specifier: ^3.3.2
|
||||
version: 3.3.2
|
||||
three:
|
||||
specifier: ^0.154.0
|
||||
version: 0.154.0
|
||||
tinycolor2:
|
||||
specifier: ^1.6.0
|
||||
version: 1.6.0
|
||||
vite-svg-loader:
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0
|
||||
@ -1424,6 +1442,17 @@ packages:
|
||||
- vue-tsc
|
||||
dev: true
|
||||
|
||||
/@nuxtjs/color-mode@3.3.0:
|
||||
resolution: {integrity: sha512-YVFNmTISke1eL7uk5p9I1suOsM222FxrqKoF13HS4x94OKCWwPLLeTCEzHZ8orzKnaFUbCXpuL4pRv8gvW+0Kw==}
|
||||
dependencies:
|
||||
'@nuxt/kit': 3.6.2
|
||||
lodash.template: 4.5.0
|
||||
pathe: 1.1.1
|
||||
transitivePeerDependencies:
|
||||
- rollup
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@nuxtjs/i18n@8.0.0-beta.13(vue@3.3.4):
|
||||
resolution: {integrity: sha512-h0OqoSSdD9MGCXYZIDpYgQNN90r7MQ/sKVtyBQnrpGQLh1pqI7uLXoAVz4l4r09JzE2nNXK5U0thcx7Tq7ONUg==}
|
||||
engines: {node: ^14.16.0 || >=16.11.0}
|
||||
@ -1677,6 +1706,10 @@ packages:
|
||||
minimatch: 9.0.3
|
||||
dev: true
|
||||
|
||||
/@tweenjs/tween.js@18.6.4:
|
||||
resolution: {integrity: sha512-lB9lMjuqjtuJrx7/kOkqQBtllspPIN+96OvTCeJ2j5FEzinoAXTdAMFnDAQT1KVPRlnYfBrqxtqP66vDM40xxQ==}
|
||||
dev: true
|
||||
|
||||
/@types/debug@4.1.8:
|
||||
resolution: {integrity: sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==}
|
||||
dependencies:
|
||||
@ -1731,6 +1764,24 @@ packages:
|
||||
'@types/node': 18.0.0
|
||||
dev: true
|
||||
|
||||
/@types/stats.js@0.17.0:
|
||||
resolution: {integrity: sha512-9w+a7bR8PeB0dCT/HBULU2fMqf6BAzvKbxFboYhmDtDkKPiyXYbjoe2auwsXlEFI7CFNMF1dCv3dFH5Poy9R1w==}
|
||||
dev: true
|
||||
|
||||
/@types/three@0.153.0:
|
||||
resolution: {integrity: sha512-L9quzIP4lsl6asDCw5zqN5opewCVOKRuB09apw5Acf2AIkoj5gLnUOY5AMB/ntFUt/QfFey0uKZaAd5t+HXSUw==}
|
||||
dependencies:
|
||||
'@tweenjs/tween.js': 18.6.4
|
||||
'@types/stats.js': 0.17.0
|
||||
'@types/webxr': 0.5.2
|
||||
fflate: 0.6.10
|
||||
lil-gui: 0.17.0
|
||||
dev: true
|
||||
|
||||
/@types/tinycolor2@1.4.3:
|
||||
resolution: {integrity: sha512-Kf1w9NE5HEgGxCRyIcRXR/ZYtDv0V8FVPtYHwLxl0O+maGX0erE77pQlD0gpP+/KByMZ87mOA79SjifhSB3PjQ==}
|
||||
dev: true
|
||||
|
||||
/@types/unist@2.0.6:
|
||||
resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
|
||||
dev: true
|
||||
@ -1739,6 +1790,10 @@ packages:
|
||||
resolution: {integrity: sha512-MFETx3tbTjE7Uk6vvnWINA/1iJ7LuMdO4fcq8UfF0pRbj01aGLduVvQcRyswuACJdpnHgg8E3rQLhaRdNEJS0w==}
|
||||
dev: true
|
||||
|
||||
/@types/webxr@0.5.2:
|
||||
resolution: {integrity: sha512-szL74BnIcok9m7QwYtVmQ+EdIKwbjPANudfuvDrAF8Cljg9MKUlIoc1w5tjj9PMpeSH3U1Xnx//czQybJ0EfSw==}
|
||||
dev: true
|
||||
|
||||
/@unhead/dom@1.1.30:
|
||||
resolution: {integrity: sha512-EvASOkk36lW5sRfIe+StCojpkPEExsQNt+cqcpdVr9iiRH54jziCDFxcLfjawc+jp4NO86KvmfHo86GIly3/SQ==}
|
||||
dependencies:
|
||||
@ -3407,6 +3462,10 @@ packages:
|
||||
web-streams-polyfill: 3.2.1
|
||||
dev: true
|
||||
|
||||
/fflate@0.6.10:
|
||||
resolution: {integrity: sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==}
|
||||
dev: true
|
||||
|
||||
/file-type@3.9.0:
|
||||
resolution: {integrity: sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@ -4412,6 +4471,10 @@ packages:
|
||||
readable-stream: 2.3.8
|
||||
dev: true
|
||||
|
||||
/lil-gui@0.17.0:
|
||||
resolution: {integrity: sha512-MVBHmgY+uEbmJNApAaPbtvNh1RCAeMnKym82SBjtp5rODTYKWtM+MXHCifLe2H2Ti1HuBGBtK/5SyG4ShQ3pUQ==}
|
||||
dev: true
|
||||
|
||||
/lilconfig@2.1.0:
|
||||
resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==}
|
||||
engines: {node: '>=10'}
|
||||
@ -4439,6 +4502,10 @@ packages:
|
||||
engines: {node: '>=14'}
|
||||
dev: true
|
||||
|
||||
/lodash._reinterpolate@3.0.0:
|
||||
resolution: {integrity: sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA==}
|
||||
dev: true
|
||||
|
||||
/lodash.debounce@4.0.8:
|
||||
resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
|
||||
dev: true
|
||||
@ -4471,6 +4538,19 @@ packages:
|
||||
resolution: {integrity: sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==}
|
||||
dev: true
|
||||
|
||||
/lodash.template@4.5.0:
|
||||
resolution: {integrity: sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==}
|
||||
dependencies:
|
||||
lodash._reinterpolate: 3.0.0
|
||||
lodash.templatesettings: 4.2.0
|
||||
dev: true
|
||||
|
||||
/lodash.templatesettings@4.2.0:
|
||||
resolution: {integrity: sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==}
|
||||
dependencies:
|
||||
lodash._reinterpolate: 3.0.0
|
||||
dev: true
|
||||
|
||||
/lodash.union@4.6.0:
|
||||
resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==}
|
||||
dev: true
|
||||
@ -4747,6 +4827,14 @@ packages:
|
||||
engines: {node: '>= 8'}
|
||||
dev: true
|
||||
|
||||
/meshline@3.1.6(three@0.154.0):
|
||||
resolution: {integrity: sha512-8JZJOdaL5oz3PI/upG8JvP/5FfzYUOhrkJ8np/WKvXzl0/PZ2V9pqTvCIjSKv+w9ccg2xb+yyBhXAwt6ier3ug==}
|
||||
peerDependencies:
|
||||
three: '>=0.137'
|
||||
dependencies:
|
||||
three: 0.154.0
|
||||
dev: true
|
||||
|
||||
/micromark-core-commonmark@1.1.0:
|
||||
resolution: {integrity: sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==}
|
||||
dependencies:
|
||||
@ -7141,6 +7229,10 @@ packages:
|
||||
any-promise: 1.3.0
|
||||
dev: true
|
||||
|
||||
/three@0.154.0:
|
||||
resolution: {integrity: sha512-Uzz8C/5GesJzv8i+Y2prEMYUwodwZySPcNhuJUdsVMH2Yn4Nm8qlbQe6qRN5fOhg55XB0WiLfTPBxVHxpE60ug==}
|
||||
dev: true
|
||||
|
||||
/through@2.3.8:
|
||||
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
|
||||
dev: true
|
||||
@ -7149,6 +7241,10 @@ packages:
|
||||
resolution: {integrity: sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==}
|
||||
dev: true
|
||||
|
||||
/tinycolor2@1.6.0:
|
||||
resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==}
|
||||
dev: true
|
||||
|
||||
/titleize@3.0.0:
|
||||
resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
BIN
public/img/emojis/four-leaf-clover_1f340.png
Normal file
After Width: | Height: | Size: 5.7 KiB |
BIN
public/img/emojis/gear_2699-fe0f.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
public/img/emojis/light-bulb_1f4a1.png
Normal file
After Width: | Height: | Size: 8.0 KiB |
BIN
public/img/emojis/package_1f4e6.png
Normal file
After Width: | Height: | Size: 6.7 KiB |
BIN
public/img/emojis/ringed-planet_1fa90.png
Normal file
After Width: | Height: | Size: 8.7 KiB |
BIN
public/img/emojis/rocket_1f680.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
public/img/emojis/warning_26a0-fe0f.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
public/img/hero/ai.png
Normal file
After Width: | Height: | Size: 366 KiB |
BIN
public/img/hero/screenshot-desktop-en.png
Normal file
After Width: | Height: | Size: 881 KiB |
BIN
public/img/hero/screenshot-desktop.png
Normal file
After Width: | Height: | Size: 641 KiB |
BIN
public/img/hero/screenshot-mobile-en.png
Normal file
After Width: | Height: | Size: 93 KiB |
BIN
public/img/hero/screenshot-mobile.png
Normal file
After Width: | Height: | Size: 123 KiB |
BIN
public/img/top-features/top-features-charts.png
Normal file
After Width: | Height: | Size: 68 KiB |
BIN
public/img/top-features/top-features-drive.png
Normal file
After Width: | Height: | Size: 298 KiB |
BIN
public/img/top-features/top-features-federation.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
public/img/top-features/top-features-note.png
Normal file
After Width: | Height: | Size: 108 KiB |
BIN
public/img/top-features/top-features-reaction.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
public/img/top-features/top-features-theme.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
public/img/top-features/top-features-thread.png
Normal file
After Width: | Height: | Size: 169 KiB |
BIN
public/img/top-features/top-features-widgets-en.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
public/img/top-features/top-features-widgets.png
Normal file
After Width: | Height: | Size: 17 KiB |
@ -2,6 +2,7 @@ import type { Config } from "tailwindcss"
|
||||
import defaultTheme from "tailwindcss/defaultTheme";
|
||||
|
||||
export default <Config> {
|
||||
darkMode: 'class',
|
||||
content: [
|
||||
'./components/**/*.{js,vue,ts}',
|
||||
"./layouts/**/*.vue",
|
||||
@ -30,6 +31,7 @@ export default <Config> {
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
'title': ['Capriola', 'GenJyuuGothicX', ...defaultTheme.fontFamily.sans],
|
||||
'sans': ['Nunito', 'GenJyuuGothicX', ...defaultTheme.fontFamily.sans],
|
||||
'content-sans': ['Nunito', 'GenJyuuGothicXP', ...defaultTheme.fontFamily.sans],
|
||||
}
|
||||
|