diff --git a/assets/vk/README.md b/assets/vk/README.md new file mode 100644 index 0000000..8ad21b8 --- /dev/null +++ b/assets/vk/README.md @@ -0,0 +1,115 @@ +# Erudit — VK loading-screen logo (Lottie) + +Animated logo for the **VK app loading screen** (shown before the SPA assets load). +The Erudit «Э» tile (score `8`) **drops in under gravity, lands with a soft squash — +its left/right edges bulging into a cushion — then springs back up**, looping. A light +"glint" is caught at the moment of the bounce. + +| File | Purpose | +|------|---------| +| `erudit-loader.json` | The Lottie to upload to VK. | +| `erudit-loader-preview.gif` | Looping preview. Rendered on a green "baize" background **only in the preview** — the real asset has a **transparent** background. | +| `build/` | The reproducible build pipeline (see *Regenerate*). | + +**VK requirements met:** 96×96 px · vector Lottie JSON · ≤ 24 KB (~11 KB) · seamless +~1.1 s loop · transparent background. + +Design reference: a photo of the physical wooden Erudit tile. + +--- + +## How it works + +A single flat layer holds the tile — a rounded-rect **face path**, the two glyphs, and +a thin border — and everything is driven by that layer's transform plus a few colour / +shape tracks. The anchor sits on the tile's **bottom edge**, so the squash happens +against the floor. + +### Bounce (gravity) +`position.y` of the bottom edge goes apex → floor → apex with **no dwell** (the apex is +an instantaneous turn-around). The fall uses a strong **ease-in** (accelerate) and the +rise a strong **ease-out** (decelerate), so it reads as real gravity. While falling fast +the tile slightly **stretches** (tall+thin); that is the squash-and-stretch setup. + +### Soft cushion (squash) +On contact the layer **squashes** (scaleY↓, scaleX↑) about the bottom anchor, with a +touch of skew. Crucially the squash/cushion is **decoupled from the fall speed** — it +eases in and out *slowly* (`ES`), so the landing feels soft even though the fall is +fast. The squash is deliberately gentle (≈ 86 % / 112 %). + +### The cushion shape (the hard part) +The face is **not** a plain rounded rect — it is a path whose left/right contour bows +out into a convex cushion on impact: +- the rest rounded-rect outline is sampled (fine corner arcs + edge mids), and on impact + every point is pushed outward by a smooth **barrel** profile `f(y) = b·(1 − (y/HH)²)` + (max at the middle, fading to zero at the top/bottom); +- so the **whole side — corners included — bows out as one piece**, never just the + straight middle; +- bezier handles are computed as a **chordal spline** (handle length ∝ the adjacent + chord), which stays smooth across the uneven corner/edge point spacing. This is what + keeps the corner↔cushion junction kink-free — both at rest and fully bulged — which a + naïve uniform Catmull-Rom (or moving discrete points with fixed tangents) does not. + +### Glint +At the bounce the face flashes a little lighter (`FACE_GLINT`) and the glyphs warm +toward brown-red (`BLACK_LIT`), then settle back. A cheap, warm "catch the light". + +### Border & background +The tile carries a thin static **border** a touch darker than the face (`BORDER`) so it +reads on any background. The **green baize background is added only when rendering the +preview GIF** — the Lottie itself is transparent. + +### Player compatibility +Only plain 2-D shapes (`ddd:0`) — no 3-D layers, expressions or effects — so every +Lottie player (lottie-web on the web, rlottie on native) renders it identically. + +--- + +## Glyphs + +`Э` (U+042D) and `8` are **real outlines from LiberationSans-Regular** — metric- +compatible with Arial, matching the game's `--font: system-ui … Arial` stack +(see `ui/src/app.css`). `build/extract.js` pulls the contours into `build/glyphs.json` +as Lottie cubic paths; a hair of same-colour stroke adds a touch of weight. + +--- + +## Regenerate + +Requirements: Node ≥ 18. `extract.js` additionally needs `opentype.js` +(`npm i opentype.js`); **`generate.js` has no dependencies**. + +```sh +cd assets/vk + +# 1. (optional) re-extract the glyphs — only if you change the font: +# default font: /usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf +node build/extract.js [/path/to/font.ttf] # -> build/glyphs.json + +# 2. build the animation (reads build/glyphs.json): +node build/generate.js erudit-loader.json # -> erudit-loader.json + +# 3. (optional) live preview — needs lottie-web (npm i lottie-web): +node build/build-preview.js erudit-loader.json preview.html +# then open preview.html in a browser. +``` + +The preview GIF is assembled by rendering the Lottie frame-by-frame (lottie-web canvas, +filled with the green baize) and stitching with `ffmpeg`; the live HTML preview is the +quickest way to eyeball changes. + +## Tunables (top of `build/generate.js`) + +| Symbol | Meaning | +|--------|---------| +| `OP`, `FR` | loop length (frames) / fps — overall speed | +| `T_HIT / T_PEAK / T_LIFT / T_REC` | beats: contact / squash peak / lift-off / cushion recovered | +| `EI / EO / ES` | easings: fast fall / fast rise / slow soft cushion | +| `APEX`, `FLOOR` | bottom-edge screen-y at the top of the bounce / at rest | +| `HW`, `HH`, `CR` | tile half-width / half-height / corner radius | +| `scl` squash values | the squash amount (scaleX↑ / scaleY↓) | +| `bulge` `b` | cushion depth (how far the sides bow out) | +| `FACE / FACE_GLINT` | wood face / glint flash | +| `BORDER` | tile rim colour | +| `BLACK / BLACK_LIT` | glyph / glyph-at-glint (brown-red) | +| preview bg `#3C7858` | the green-baize colour used **only** in the GIF | diff --git a/assets/vk/build/build-preview.js b/assets/vk/build/build-preview.js new file mode 100644 index 0000000..a490ee4 --- /dev/null +++ b/assets/vk/build/build-preview.js @@ -0,0 +1,31 @@ +'use strict'; +const fs = require('fs'); +const data = fs.readFileSync(process.argv[2] || 'erudit.json', 'utf8'); +const lottiePath = __dirname + '/node_modules/lottie-web/build/player/lottie.min.js'; +const html = `
+ +`; +fs.writeFileSync(process.argv[3] || 'preview.html', html); +console.log('wrote', process.argv[3] || 'preview.html'); diff --git a/assets/vk/build/extract.js b/assets/vk/build/extract.js new file mode 100644 index 0000000..a225381 --- /dev/null +++ b/assets/vk/build/extract.js @@ -0,0 +1,67 @@ +'use strict'; +// Extract the Cyrillic "Э" (U+042D) and the digit "8" outlines from a grotesque +// font (LiberationSans = Arial-metric, matching the game's system-ui/Arial stack) +// and emit Lottie cubic-bezier contours, centred at the origin, y-down. +const opentype = require('opentype.js'); +const fs = require('fs'); + +const FONT = process.argv[2] || '/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf'; +const b = fs.readFileSync(FONT); +const font = opentype.parse(b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength)); +const FS = 1000; // em scale + +function glyphContours(ch) { + const p = font.charToGlyph(ch).getPath(0, 0, FS); // baseline at y=0, y-down + const contours = []; + let cur = null, prev = null; + for (const c of p.commands) { + if (c.type === 'M') { + if (cur) contours.push(cur); + cur = [{ v: [c.x, c.y], i: [c.x, c.y], o: [c.x, c.y] }]; + prev = { x: c.x, y: c.y }; + } else if (c.type === 'L') { + cur.push({ v: [c.x, c.y], i: [c.x, c.y], o: [c.x, c.y] }); + prev = { x: c.x, y: c.y }; + } else if (c.type === 'C') { + cur[cur.length - 1].o = [c.x1, c.y1]; + cur.push({ v: [c.x, c.y], i: [c.x2, c.y2], o: [c.x, c.y] }); + prev = { x: c.x, y: c.y }; + } else if (c.type === 'Q') { + const c1 = [prev.x + 2 / 3 * (c.x1 - prev.x), prev.y + 2 / 3 * (c.y1 - prev.y)]; + const c2 = [c.x + 2 / 3 * (c.x1 - c.x), c.y + 2 / 3 * (c.y1 - c.y)]; + cur[cur.length - 1].o = c1; + cur.push({ v: [c.x, c.y], i: c2, o: [c.x, c.y] }); + prev = { x: c.x, y: c.y }; + } else if (c.type === 'Z') { + if (cur && cur.length > 1) { + const last = cur[cur.length - 1], first = cur[0]; + if (Math.hypot(last.v[0] - first.v[0], last.v[1] - first.v[1]) < 1e-3) { + first.i = last.i; // fold the duplicate closing point into the first + cur.pop(); + } + } + if (cur) { contours.push(cur); cur = null; } + } + } + if (cur) contours.push(cur); + + let minx = Infinity, miny = Infinity, maxx = -Infinity, maxy = -Infinity; + const out = contours.map(ct => { + const v = [], i = [], o = []; + ct.forEach(pt => { + v.push(pt.v); + i.push([pt.i[0] - pt.v[0], pt.i[1] - pt.v[1]]); + o.push([pt.o[0] - pt.v[0], pt.o[1] - pt.v[1]]); + minx = Math.min(minx, pt.v[0]); maxx = Math.max(maxx, pt.v[0]); + miny = Math.min(miny, pt.v[1]); maxy = Math.max(maxy, pt.v[1]); + }); + return { i, o, v, c: true }; + }); + return { contours: out, bbox: { x: minx, y: miny, w: maxx - minx, h: maxy - miny } }; +} + +const E = glyphContours('Э'); +const D8 = glyphContours('8'); +fs.writeFileSync('glyphs.json', JSON.stringify({ em: FS, E, D8 })); +console.log('Э bbox', E.bbox, 'contours', E.contours.map(c => c.v.length)); +console.log('8 bbox', D8.bbox, 'contours', D8.contours.map(c => c.v.length)); diff --git a/assets/vk/build/generate.js b/assets/vk/build/generate.js new file mode 100644 index 0000000..5e515e9 --- /dev/null +++ b/assets/vk/build/generate.js @@ -0,0 +1,153 @@ +'use strict'; +// VK preloader Lottie: a flat wooden Erudit tile ("Э" + score "8") that drops in +// under gravity, squashes on impact (convex cushion bulging out the left/right edges +// + a touch of skew), and springs back up — looping. A light "glint" is caught at the +// moment of the bounce. Pure 2D shapes (ddd:0). Glyphs are real LiberationSans +// outlines (Arial-metric, = the game's font stack); see extract.js -> glyphs.json. +const fs = require('fs'); +const G = JSON.parse(fs.readFileSync(__dirname + '/glyphs.json', 'utf8')); + +// ---- canvas / timing ------------------------------------------------------- +const W = 96, H = 96, cx = 48; +const FR = 30, OP = 34; // ~1.13 s loop (dynamic, 25% faster) +// keyframe beats (frames): fast fall -> soft squash -> lift -> fast rise -> apex (no dwell) +const T_HIT = 13, T_PEAK = 18, T_LIFT = 19, T_REC = 28; + +// ---- geometry -------------------------------------------------------------- +const HW = 26, HH = 26, CR = 6; // tile half-width / half-height / corner radius +const FLOOR = 90, APEX = 60; // bottom-centre screen-y at rest / at the top of the bounce + +// ---- palette --------------------------------------------------------------- +const col = h => [parseInt(h.slice(1,3),16)/255, parseInt(h.slice(3,5),16)/255, parseInt(h.slice(5,7),16)/255]; +const FACE = col('#D9B978'); // wood face (flat) +const FACE_GLINT = col('#E7CD95'); // face flash caught on impact (gentle, not blinding) +const BORDER = col('#B49559'); // tile rim: a touch darker than the face, reads on any bg +const BLACK = col('#1A1A1A'); +const BLACK_LIT = col('#421A0B'); // glyph warms to brown-red on the glint +const lerp = (a, b, t) => a.map((v, i) => v + (b[i] - v) * t); + +// ---- property / easing helpers --------------------------------------------- +const still = v => ({ a: 0, k: v }); +const EI = { o: { x: [0.82], y: [0] }, i: { x: [1], y: [1] } }; // strong accelerate (gravity fall) +const EO = { o: { x: [0], y: [0] }, i: { x: [0.18], y: [1] } }; // strong decelerate (rise to apex) +const ES = { o: { x: [0.42], y: [0] }, i: { x: [0.58], y: [1] } }; // soft/slow (the cushion) +function anim(samples) { // samples: {t, v, e?} ; e = easing toward the NEXT key + return { a: 1, k: samples.map((s, idx) => { + const kf = { t: s.t, s: s.v }; + if (idx < samples.length - 1) { const e = s.e || ES; kf.o = e.o; kf.i = e.i; } + return kf; + }) }; +} +const animColor = s => anim(s.map(x => ({ t: x.t, v: [...x.v, 1], e: x.e }))); + +// ---- shape helpers --------------------------------------------------------- +const tr = () => ({ ty: 'tr', p: still([0,0]), a: still([0,0]), s: still([100,100]), r: still(0), o: still(100) }); +const grp = (items, nm) => ({ ty: 'gr', it: [...items, tr()], nm }); +const fill = c => ({ ty: 'fl', c, o: still(100), r: 1, bm: 0 }); +const stroke = (c,w) => ({ ty: 'st', c: still(c), o: still(100), w: still(w), lc: 2, lj: 2, ml: 4, bm: 0 }); +function glyph(gd, hpx, px, py, bold, colour, nm) { // font glyph -> filled+stroked group + const sc = hpx / gd.bbox.h; + const bcx = gd.bbox.x + gd.bbox.w / 2, bcy = gd.bbox.y + gd.bbox.h / 2; + const shapes = gd.contours.map(ct => ({ + ty: 'sh', d: 1, ks: still({ + i: ct.i.map(p => [p[0]*sc, p[1]*sc]), o: ct.o.map(p => [p[0]*sc, p[1]*sc]), + v: ct.v.map(p => [(p[0]-bcx)*sc + px, (p[1]-bcy)*sc + py]), c: true, + }), + })); + return grp([...shapes, fill(colour), stroke(BLACK, bold)], nm); +} + +// Sample the rest rounded-rect outline (clockwise): fine corner arcs + one mid point +// per straight edge, so a smooth interpolant reproduces it cleanly. +function arc(ccx, ccy, a0, a1, n) { + const out = []; + for (let i = 0; i < n; i++) { const a = (a0 + (a1 - a0) * i / (n - 1)) * Math.PI / 180; out.push([ccx + CR*Math.cos(a), ccy + CR*Math.sin(a)]); } + return out; +} +const REST = (() => { + const NC = 7, yi = HH - CR, xi = HW - CR; + const C = [ arc(xi,-yi,-90,0,NC), arc(xi,yi,0,90,NC), arc(-xi,yi,90,180,NC), arc(-xi,-yi,180,270,NC) ]; + const pts = []; + for (let k = 0; k < 4; k++) { + pts.push(...C[k]); + const a = C[k][NC-1], b = C[(k+1)%4][0]; + pts.push([(a[0]+b[0])/2, (a[1]+b[1])/2]); // straight-edge mid point (keeps the edge straight) + } + return pts; +})(); +// The whole left/right contour (corners + side) bows out by one smooth barrel profile; +// Catmull-Rom tangents (from neighbours) make every junction C1-smooth -> no kink, the +// corners follow the cushion and the cushion melts into the corners, bulged or not. +function facePath(b) { + const f = y => b * (1 - (y/HH) * (y/HH)); // 0 at top/bottom, max at the middle + const V = REST.map(([x, y]) => [x + Math.sign(x) * f(y), y]); // push the sides out, top/bottom stay + const n = V.length, I = [], O = []; + for (let k = 0; k < n; k++) { + const p0 = V[(k-1+n)%n], p1 = V[k], p2 = V[(k+1)%n]; + let dx = p2[0]-p0[0], dy = p2[1]-p0[1]; // tangent direction (neighbours) + const dl = Math.hypot(dx, dy) || 1; dx /= dl; dy /= dl; + const ln = Math.hypot(p2[0]-p1[0], p2[1]-p1[1]) / 3; // handles ~ 1/3 of the adjacent chord + const lp = Math.hypot(p1[0]-p0[0], p1[1]-p0[1]) / 3; // -> chordal spline, no overshoot/facets + O.push([dx*ln, dy*ln]); I.push([-dx*lp, -dy*lp]); + } + return { i: I, o: O, v: V, c: true }; +} +const facePathKeys = samples => ({ a: 1, k: samples.map((s, idx) => { + const kf = { t: s.t, s: [facePath(s.b)] }; + if (idx < samples.length - 1) { const e = s.e || ES; kf.o = e.o; kf.i = e.i; } + return kf; +}) }); + +// ---- animation tracks ------------------------------------------------------ +// bottom-centre screen-y (the tile rests/squashes on its bottom edge) +const posY = anim([ + { t: 0, v: [cx, APEX, 0], e: EI }, // apex -> immediately falls (no dwell) + { t: T_HIT, v: [cx, FLOOR, 0], e: ES }, // FAST fall (accelerate) -> floor + { t: T_LIFT, v: [cx, FLOOR, 0], e: EO }, // sit on the floor while the cushion absorbs + { t: OP, v: [cx, APEX, 0] }, // FAST rise (decelerate) -> apex = loop start +]); +// scale about the bottom anchor: stretch while falling, gentle/slow cushion squash on impact +const scl = anim([ + { t: 0, v: [100, 100, 100], e: ES }, + { t: T_HIT-5, v: [95, 106, 100], e: ES }, // stretch while falling fast + { t: T_HIT, v: [104, 95, 100], e: ES }, // touch down + { t: T_PEAK, v: [112, 86, 100], e: ES }, // soft squash peak (gentler, less plче) + { t: T_REC, v: [100, 100, 100], e: ES }, // slow cushion release -> soft landing + { t: OP, v: [100, 100, 100] }, +]); +// a touch of skew through the squash (organic deform), back to 0 +const skw = anim([ + { t: T_HIT, v: [0], e: ES }, { t: T_PEAK, v: [4], e: ES }, { t: T_REC, v: [0] }, { t: OP, v: [0] }, +]); +// left/right cushion bulge: 0 -> gentle peak -> 0 (never inward) +const bulge = facePathKeys([ + { t: T_HIT, b: 0, e: ES }, { t: T_PEAK, b: 4, e: ES }, { t: T_REC, b: 0 }, { t: OP, b: 0 }, +]); +// glint: face + glyph catch the light at the bounce +const faceCol = animColor([ + { t: T_HIT-2, v: FACE, e: ES }, { t: T_PEAK, v: FACE_GLINT, e: ES }, { t: T_REC, v: FACE }, { t: OP, v: FACE }, +]); +const glyphCol = animColor([ + { t: T_HIT-2, v: BLACK, e: ES }, { t: T_PEAK, v: BLACK_LIT, e: ES }, { t: T_REC, v: BLACK }, { t: OP, v: BLACK }, +]); + +// ---- layer (face + glyphs share the bounce/squash transform) --------------- +const faceShape = grp([ { ty: 'sh', d: 1, ks: bulge }, fill(faceCol), stroke(BORDER, 1.8) ], 'face'); +const glyphE = glyph(G.E, 32, 0, -1, 1.0, glyphCol, 'E'); // centred Э +const glyph8 = glyph(G.D8, 8.5, HW-6.5, HH-7.5, 0.5, glyphCol, 'score'); // 8 in the corner + +const tile = { + ddd: 0, ind: 1, ty: 4, nm: 'tile', sr: 1, + ks: { o: still(100), r: still(0), p: posY, a: still([0, HH, 0]), s: scl, sk: skw, sa: still(0) }, + ao: 0, shapes: [ glyph8, glyphE, faceShape ], ip: 0, op: OP, st: 0, bm: 0, +}; + +const root = { + v: '5.7.4', fr: FR, ip: 0, op: OP, w: W, h: H, nm: 'erudit-vk-loader', ddd: 0, + assets: [], layers: [ tile ], markers: [], +}; + +const out = process.argv[2] || 'erudit.json'; +const round = (k, v) => (typeof v === 'number' ? Math.round(v * 100) / 100 : v); +fs.writeFileSync(out, JSON.stringify(root, round)); +console.log('wrote', out, fs.statSync(out).size, 'bytes'); diff --git a/assets/vk/build/glyphs.json b/assets/vk/build/glyphs.json new file mode 100644 index 0000000..0771598 --- /dev/null +++ b/assets/vk/build/glyphs.json @@ -0,0 +1 @@ +{"em":1000,"E":{"contours":[{"i":[[0,0],[0,0],[35.481770833333314,-21.97265625],[14.6484375,-38.41145833333337],[0,0],[0,0],[-49.15364583333333,28.64583333333337],[-73.2421875,0],[0,0],[-59.24479166666663,-62.17447916666663],[0,-109.70052083333331],[0,0],[26.3671875,-53.7109375],[50.29296875,-28.971354166666664],[69.98697916666663,0],[0,0],[59.89583333333333,121.09375],[0,0],[0,0],[-37.434895833333314,-24.90234375],[-47.200520833333314,0],[0,0],[-43.45703125,44.10807291666666],[-6.184895833333371,74.86979166666666],[0,0],[0,0],[0,0],[0,0],[41.82942708333337,41.17838541666663],[69.3359375,0]],"o":[[0,0],[-49.479166666666686,0],[-35.481770833333314,21.97265625],[0,0],[0,0],[23.111979166666657,-56.31510416666663],[49.153645833333314,-28.64583333333337],[0,0],[104.81770833333331,0],[59.24479166666663,62.17447916666663],[0,0],[0,72.59114583333331],[-26.3671875,53.7109375],[-50.29296875,28.971354166666664],[0,0],[-138.671875,0],[0,0],[0,0],[22.4609375,45.57291666666666],[37.434895833333314,24.90234375],[0,0],[69.3359375,0],[43.45703125,-44.10807291666666],[0,0],[0,0],[0,0],[0,0],[-6.8359375,-74.54427083333331],[-41.829427083333314,-41.17838541666663],[0,0]],"v":[[348.14453125,-622.0703125],[348.14453125,-622.0703125],[220.703125,-589.111328125],[145.5078125,-498.53515625],[145.5078125,-498.53515625],[57.12890625,-527.83203125],[165.52734375,-655.2734375],[349.12109375,-698.2421875],[349.12109375,-698.2421875],[595.21484375,-604.98046875],[684.08203125,-347.16796875],[684.08203125,-347.16796875],[644.53125,-157.71484375],[529.541015625,-33.69140625],[349.12109375,9.765625],[349.12109375,9.765625],[51.26953125,-171.875],[51.26953125,-171.875],[127.44140625,-209.9609375],[217.28515625,-104.248046875],[344.23828125,-66.89453125],[344.23828125,-66.89453125],[513.427734375,-133.056640625],[587.890625,-311.5234375],[587.890625,-311.5234375],[268.5546875,-311.5234375],[268.5546875,-386.71875],[587.890625,-386.71875],[514.892578125,-560.302734375],[348.14453125,-622.0703125]],"c":true}],"bbox":{"x":51.26953125,"y":-698.2421875,"w":632.8125,"h":708.0078125}},"D8":{"contours":[{"i":[[0,0],[0,0],[40.364583333333314,-35.48177083333333],[75.52083333333331,0],[0,0],[41.50390625,34.830729166666664],[0,64.12760416666666],[0,0],[-25.71614583333333,30.598958333333314],[-40.0390625,6.510416666666686],[0,0],[0,0],[21.647135416666657,29.296875],[0,39.388020833333314],[0,0],[-39.22526041666666,32.55208333333337],[-66.08072916666666,0],[0,0],[-39.225260416666686,-31.90104166666663],[0,-54.36197916666663],[0,0],[21.809895833333314,-29.296875],[37.760416666666686,-7.486979166666686],[0,0],[0,0],[-24.4140625,-30.110677083333314],[0,-45.247395833333314]],"o":[[0,0],[0,63.4765625],[-40.364583333333314,35.48177083333333],[0,0],[-73.56770833333331,0],[-41.50390625,-34.83072916666666],[0,0],[0,-44.921875],[25.71614583333333,-30.598958333333314],[0,0],[0,0],[-37.434895833333314,-8.7890625],[-21.647135416666657,-29.296875],[0,0],[0,-52.40885416666663],[39.22526041666666,-32.55208333333337],[0,0],[67.70833333333331,0],[39.225260416666686,31.90104166666663],[0,0],[0,39.388020833333314],[-21.809895833333314,29.296875],[0,0],[0,0],[43.9453125,7.161458333333314],[24.4140625,30.110677083333314],[0,0]],"v":[[512.6953125,-191.89453125],[512.6953125,-191.89453125],[452.1484375,-43.45703125],[278.3203125,9.765625],[278.3203125,9.765625],[105.712890625,-42.48046875],[43.45703125,-190.91796875],[43.45703125,-190.91796875],[82.03125,-304.19921875],[180.6640625,-359.86328125],[180.6640625,-359.86328125],[180.6640625,-361.81640625],[92.041015625,-418.9453125],[59.5703125,-521.97265625],[59.5703125,-521.97265625],[118.408203125,-649.4140625],[276.3671875,-698.2421875],[276.3671875,-698.2421875],[436.767578125,-650.390625],[495.60546875,-520.99609375],[495.60546875,-520.99609375],[462.890625,-417.96875],[373.53515625,-362.79296875],[373.53515625,-362.79296875],[373.53515625,-360.83984375],[476.07421875,-304.931640625],[512.6953125,-191.89453125]],"c":true},{"i":[[0,0],[0,0],[85.28645833333331,0],[0,0],[21.647135416666657,-19.53125],[0,-38.73697916666663],[0,0],[-22.298177083333343,-20.670572916666686],[-40.69010416666666,0],[0,0],[-21.647135416666686,19.04296875],[0,42.643229166666686]],"o":[[0,0],[0,-77.79947916666663],[0,0],[-41.341145833333314,0],[-21.647135416666657,19.53125],[0,0],[0,39.388020833333314],[22.298177083333343,20.670572916666686],[0,0],[41.341145833333314,0],[21.647135416666686,-19.04296875],[0,0]],"v":[[404.296875,-516.11328125],[404.296875,-516.11328125],[276.3671875,-632.8125],[276.3671875,-632.8125],[181.884765625,-603.515625],[149.4140625,-516.11328125],[149.4140625,-516.11328125],[182.861328125,-426.025390625],[277.34375,-395.01953125],[277.34375,-395.01953125],[371.826171875,-423.583984375],[404.296875,-516.11328125]],"c":true},{"i":[[0,0],[0,0],[25.390625,21.647135416666686],[45.8984375,0],[0,0],[25.065104166666657,-23.274739583333314],[0,-40.69010416666666],[0,0],[-96.6796875,0],[0,0],[-23.4375,22.94921875],[0,50.130208333333314]],"o":[[0,0],[0,-42.64322916666666],[-25.390625,-21.647135416666686],[0,0],[-44.59635416666666,0],[-25.065104166666657,23.274739583333314],[0,0],[0,94.7265625],[0,0],[47.8515625,0],[23.4375,-22.94921875],[0,0]],"v":[[421.38671875,-200.1953125],[421.38671875,-200.1953125],[383.30078125,-296.630859375],[276.3671875,-329.1015625],[276.3671875,-329.1015625],[171.875,-294.189453125],[134.27734375,-198.2421875],[134.27734375,-198.2421875],[279.296875,-56.15234375],[279.296875,-56.15234375],[386.23046875,-90.576171875],[421.38671875,-200.1953125]],"c":true}],"bbox":{"x":43.45703125,"y":-698.2421875,"w":469.23828125,"h":708.0078125}}} \ No newline at end of file diff --git a/assets/vk/erudit-loader-preview.gif b/assets/vk/erudit-loader-preview.gif new file mode 100644 index 0000000..141df50 Binary files /dev/null and b/assets/vk/erudit-loader-preview.gif differ diff --git a/assets/vk/erudit-loader.json b/assets/vk/erudit-loader.json new file mode 100644 index 0000000..13bd1c2 --- /dev/null +++ b/assets/vk/erudit-loader.json @@ -0,0 +1 @@ +{"v":"5.7.4","fr":30,"ip":0,"op":34,"w":96,"h":96,"nm":"erudit-vk-loader","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"tile","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"t":0,"s":[48,60,0],"o":{"x":[0.82],"y":[0]},"i":{"x":[1],"y":[1]}},{"t":13,"s":[48,90,0],"o":{"x":[0.42],"y":[0]},"i":{"x":[0.58],"y":[1]}},{"t":19,"s":[48,90,0],"o":{"x":[0],"y":[0]},"i":{"x":[0.18],"y":[1]}},{"t":34,"s":[48,60,0]}]},"a":{"a":0,"k":[0,26,0]},"s":{"a":1,"k":[{"t":0,"s":[100,100,100],"o":{"x":[0.42],"y":[0]},"i":{"x":[0.58],"y":[1]}},{"t":8,"s":[95,106,100],"o":{"x":[0.42],"y":[0]},"i":{"x":[0.58],"y":[1]}},{"t":13,"s":[104,95,100],"o":{"x":[0.42],"y":[0]},"i":{"x":[0.58],"y":[1]}},{"t":18,"s":[112,86,100],"o":{"x":[0.42],"y":[0]},"i":{"x":[0.58],"y":[1]}},{"t":28,"s":[100,100,100],"o":{"x":[0.42],"y":[0]},"i":{"x":[0.58],"y":[1]}},{"t":34,"s":[100,100,100]}]},"sk":{"a":1,"k":[{"t":13,"s":[0],"o":{"x":[0.42],"y":[0]},"i":{"x":[0.58],"y":[1]}},{"t":18,"s":[4],"o":{"x":[0.42],"y":[0]},"i":{"x":[0.58],"y":[1]}},{"t":28,"s":[0],"o":{"x":[0.42],"y":[0]},"i":{"x":[0.58],"y":[1]}},{"t":34,"s":[0]}]},"sa":{"a":0,"k":0}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0.48,-0.43],[0.91,0],[0,0],[0.5,0.42],[0,0.77],[0,0],[-0.31,0.37],[-0.48,0.08],[0,0],[0,0],[0.26,0.35],[0,0.47],[0,0],[-0.47,0.39],[-0.79,0],[0,0],[-0.47,-0.38],[0,-0.65],[0,0],[0.26,-0.35],[0.45,-0.09],[0,0],[0,0],[-0.29,-0.36],[0,-0.54]],"o":[[0,0],[0,0.76],[-0.48,0.43],[0,0],[-0.88,0],[-0.5,-0.42],[0,0],[0,-0.54],[0.31,-0.37],[0,0],[0,0],[-0.45,-0.11],[-0.26,-0.35],[0,0],[0,-0.63],[0.47,-0.39],[0,0],[0.81,0],[0.47,0.38],[0,0],[0,0.47],[-0.26,0.35],[0,0],[0,0],[0.53,0.09],[0.29,0.36],[0,0]],"v":[[22.32,20.33],[22.32,20.33],[21.59,22.11],[19.5,22.75],[19.5,22.75],[17.43,22.12],[16.68,20.34],[16.68,20.34],[17.15,18.98],[18.33,18.31],[18.33,18.31],[18.33,18.29],[17.27,17.6],[16.88,16.37],[16.88,16.37],[17.58,14.84],[19.48,14.25],[19.48,14.25],[21.41,14.82],[22.11,16.38],[22.11,16.38],[21.72,17.61],[20.65,18.28],[20.65,18.28],[20.65,18.3],[21.88,18.97],[22.32,20.33]],"c":true}}},{"ty":"sh","d":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[1.02,0],[0,0],[0.26,-0.23],[0,-0.47],[0,0],[-0.27,-0.25],[-0.49,0],[0,0],[-0.26,0.23],[0,0.51]],"o":[[0,0],[0,-0.93],[0,0],[-0.5,0],[-0.26,0.23],[0,0],[0,0.47],[0.27,0.25],[0,0],[0.5,0],[0.26,-0.23],[0,0]],"v":[[21.02,16.44],[21.02,16.44],[19.48,15.04],[19.48,15.04],[18.35,15.39],[17.96,16.44],[17.96,16.44],[18.36,17.52],[19.49,17.89],[19.49,17.89],[20.63,17.55],[21.02,16.44]],"c":true}}},{"ty":"sh","d":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0.3,0.26],[0.55,0],[0,0],[0.3,-0.28],[0,-0.49],[0,0],[-1.16,0],[0,0],[-0.28,0.28],[0,0.6]],"o":[[0,0],[0,-0.51],[-0.3,-0.26],[0,0],[-0.54,0],[-0.3,0.28],[0,0],[0,1.14],[0,0],[0.57,0],[0.28,-0.28],[0,0]],"v":[[21.22,20.23],[21.22,20.23],[20.76,19.07],[19.48,18.68],[19.48,18.68],[18.23,19.1],[17.77,20.25],[17.77,20.25],[19.51,21.96],[19.51,21.96],[20.8,21.55],[21.22,20.23]],"c":true}}},{"ty":"fl","c":{"a":1,"k":[{"t":11,"s":[0.1,0.1,0.1,1],"o":{"x":[0.42],"y":[0]},"i":{"x":[0.58],"y":[1]}},{"t":18,"s":[0.26,0.1,0.04,1],"o":{"x":[0.42],"y":[0]},"i":{"x":[0.58],"y":[1]}},{"t":28,"s":[0.1,0.1,0.1,1],"o":{"x":[0.42],"y":[0]},"i":{"x":[0.58],"y":[1]}},{"t":34,"s":[0.1,0.1,0.1,1]}]},"o":{"a":0,"k":100},"r":1,"bm":0},{"ty":"st","c":{"a":0,"k":[0.1,0.1,0.1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0.5},"lc":2,"lj":2,"ml":4,"bm":0},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100}}],"nm":"score"},{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[1.6,-0.99],[0.66,-1.74],[0,0],[0,0],[-2.22,1.29],[-3.31,0],[0,0],[-2.68,-2.81],[0,-4.96],[0,0],[1.19,-2.43],[2.27,-1.31],[3.16,0],[0,0],[2.71,5.47],[0,0],[0,0],[-1.69,-1.13],[-2.13,0],[0,0],[-1.96,1.99],[-0.28,3.38],[0,0],[0,0],[0,0],[0,0],[1.89,1.86],[3.13,0]],"o":[[0,0],[-2.24,0],[-1.6,0.99],[0,0],[0,0],[1.04,-2.55],[2.22,-1.29],[0,0],[4.74,0],[2.68,2.81],[0,0],[0,3.28],[-1.19,2.43],[-2.27,1.31],[0,0],[-6.27,0],[0,0],[0,0],[1.02,2.06],[1.69,1.13],[0,0],[3.13,0],[1.96,-1.99],[0,0],[0,0],[0,0],[0,0],[-0.31,-3.37],[-1.89,-1.86],[0,0]],"v":[[-0.88,-13.56],[-0.88,-13.56],[-6.64,-12.07],[-10.04,-7.97],[-10.04,-7.97],[-14.04,-9.3],[-9.14,-15.06],[-0.84,-17],[-0.84,-17],[10.28,-12.78],[14.3,-1.13],[14.3,-1.13],[12.51,7.43],[7.32,13.04],[-0.84,15],[-0.84,15],[-14.3,6.79],[-14.3,6.79],[-10.86,5.07],[-6.8,9.85],[-1.06,11.54],[-1.06,11.54],[6.59,8.54],[9.95,0.48],[9.95,0.48],[-4.48,0.48],[-4.48,-2.92],[9.95,-2.92],[6.65,-10.77],[-0.88,-13.56]],"c":true}}},{"ty":"fl","c":{"a":1,"k":[{"t":11,"s":[0.1,0.1,0.1,1],"o":{"x":[0.42],"y":[0]},"i":{"x":[0.58],"y":[1]}},{"t":18,"s":[0.26,0.1,0.04,1],"o":{"x":[0.42],"y":[0]},"i":{"x":[0.58],"y":[1]}},{"t":28,"s":[0.1,0.1,0.1,1],"o":{"x":[0.42],"y":[0]},"i":{"x":[0.58],"y":[1]}},{"t":34,"s":[0.1,0.1,0.1,1]}]},"o":{"a":0,"k":100},"r":1,"bm":0},{"ty":"st","c":{"a":0,"k":[0.1,0.1,0.1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"ml":4,"bm":0},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100}}],"nm":"E"},{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":1,"k":[{"t":13,"s":[{"i":[[-6.67,-0.06],[-0.5,-0.14],[-0.45,-0.26],[-0.37,-0.37],[-0.26,-0.45],[-0.14,-0.5],[0,-0.52],[0,-6.67],[0.06,-6.67],[0.14,-0.5],[0.26,-0.45],[0.37,-0.37],[0.45,-0.26],[0.5,-0.14],[0.52,0],[6.67,0],[6.67,0.06],[0.5,0.14],[0.45,0.26],[0.37,0.37],[0.26,0.45],[0.14,0.5],[0,0.52],[0,6.67],[-0.06,6.67],[-0.14,0.5],[-0.26,0.45],[-0.37,0.37],[-0.45,0.26],[-0.5,0.14],[-0.52,0],[-6.67,0]],"o":[[0.52,0],[0.5,0.14],[0.45,0.26],[0.37,0.37],[0.26,0.45],[0.14,0.5],[0.06,6.67],[0,6.67],[0,0.52],[-0.14,0.5],[-0.26,0.45],[-0.37,0.37],[-0.45,0.26],[-0.5,0.14],[-6.67,0.06],[-6.67,0],[-0.52,0],[-0.5,-0.14],[-0.45,-0.26],[-0.37,-0.37],[-0.26,-0.45],[-0.14,-0.5],[-0.06,-6.67],[0,-6.67],[0,-0.52],[0.14,-0.5],[0.26,-0.45],[0.37,-0.37],[0.45,-0.26],[0.5,-0.14],[6.67,-0.06],[6.67,0]],"v":[[20,-26],[21.55,-25.8],[23,-25.2],[24.24,-24.24],[25.2,-23],[25.8,-21.55],[26,-20],[26,0],[26,20],[25.8,21.55],[25.2,23],[24.24,24.24],[23,25.2],[21.55,25.8],[20,26],[0,26],[-20,26],[-21.55,25.8],[-23,25.2],[-24.24,24.24],[-25.2,23],[-25.8,21.55],[-26,20],[-26,0],[-26,-20],[-25.8,-21.55],[-25.2,-23],[-24.24,-24.24],[-23,-25.2],[-21.55,-25.8],[-20,-26],[0,-26]],"c":true}],"o":{"x":[0.42],"y":[0]},"i":{"x":[0.58],"y":[1]}},{"t":18,"s":[{"i":[[-6.67,-0.06],[-0.53,-0.13],[-0.52,-0.26],[-0.47,-0.37],[-0.39,-0.46],[-0.27,-0.52],[-0.08,-0.55],[0,-6.71],[0.91,-6.65],[0.26,-0.49],[0.38,-0.44],[0.47,-0.37],[0.54,-0.26],[0.56,-0.14],[0.54,-0.01],[6.67,0],[6.67,0.06],[0.53,0.13],[0.52,0.26],[0.47,0.37],[0.39,0.46],[0.27,0.52],[0.08,0.55],[0,6.71],[-0.91,6.65],[-0.26,0.49],[-0.38,0.44],[-0.47,0.37],[-0.54,0.26],[-0.56,0.14],[-0.54,0.01],[-6.67,0]],"o":[[0.54,0.01],[0.56,0.14],[0.54,0.26],[0.47,0.37],[0.38,0.44],[0.26,0.49],[0.91,6.65],[0,6.71],[-0.08,0.55],[-0.27,0.52],[-0.39,0.46],[-0.47,0.37],[-0.52,0.26],[-0.53,0.13],[-6.67,0.06],[-6.67,0],[-0.54,-0.01],[-0.56,-0.14],[-0.54,-0.26],[-0.47,-0.37],[-0.38,-0.44],[-0.26,-0.49],[-0.91,-6.65],[0,-6.71],[0.08,-0.55],[0.27,-0.52],[0.39,-0.46],[0.47,-0.37],[0.52,-0.26],[0.53,-0.13],[6.67,-0.06],[6.67,0]],"v":[[20,-26],[21.62,-25.8],[23.24,-25.2],[24.77,-24.24],[26.07,-23],[27.05,-21.55],[27.63,-20],[30,0],[27.63,20],[27.05,21.55],[26.07,23],[24.77,24.24],[23.24,25.2],[21.62,25.8],[20,26],[0,26],[-20,26],[-21.62,25.8],[-23.24,25.2],[-24.77,24.24],[-26.07,23],[-27.05,21.55],[-27.63,20],[-30,0],[-27.63,-20],[-27.05,-21.55],[-26.07,-23],[-24.77,-24.24],[-23.24,-25.2],[-21.62,-25.8],[-20,-26],[0,-26]],"c":true}],"o":{"x":[0.42],"y":[0]},"i":{"x":[0.58],"y":[1]}},{"t":28,"s":[{"i":[[-6.67,-0.06],[-0.5,-0.14],[-0.45,-0.26],[-0.37,-0.37],[-0.26,-0.45],[-0.14,-0.5],[0,-0.52],[0,-6.67],[0.06,-6.67],[0.14,-0.5],[0.26,-0.45],[0.37,-0.37],[0.45,-0.26],[0.5,-0.14],[0.52,0],[6.67,0],[6.67,0.06],[0.5,0.14],[0.45,0.26],[0.37,0.37],[0.26,0.45],[0.14,0.5],[0,0.52],[0,6.67],[-0.06,6.67],[-0.14,0.5],[-0.26,0.45],[-0.37,0.37],[-0.45,0.26],[-0.5,0.14],[-0.52,0],[-6.67,0]],"o":[[0.52,0],[0.5,0.14],[0.45,0.26],[0.37,0.37],[0.26,0.45],[0.14,0.5],[0.06,6.67],[0,6.67],[0,0.52],[-0.14,0.5],[-0.26,0.45],[-0.37,0.37],[-0.45,0.26],[-0.5,0.14],[-6.67,0.06],[-6.67,0],[-0.52,0],[-0.5,-0.14],[-0.45,-0.26],[-0.37,-0.37],[-0.26,-0.45],[-0.14,-0.5],[-0.06,-6.67],[0,-6.67],[0,-0.52],[0.14,-0.5],[0.26,-0.45],[0.37,-0.37],[0.45,-0.26],[0.5,-0.14],[6.67,-0.06],[6.67,0]],"v":[[20,-26],[21.55,-25.8],[23,-25.2],[24.24,-24.24],[25.2,-23],[25.8,-21.55],[26,-20],[26,0],[26,20],[25.8,21.55],[25.2,23],[24.24,24.24],[23,25.2],[21.55,25.8],[20,26],[0,26],[-20,26],[-21.55,25.8],[-23,25.2],[-24.24,24.24],[-25.2,23],[-25.8,21.55],[-26,20],[-26,0],[-26,-20],[-25.8,-21.55],[-25.2,-23],[-24.24,-24.24],[-23,-25.2],[-21.55,-25.8],[-20,-26],[0,-26]],"c":true}],"o":{"x":[0.42],"y":[0]},"i":{"x":[0.58],"y":[1]}},{"t":34,"s":[{"i":[[-6.67,-0.06],[-0.5,-0.14],[-0.45,-0.26],[-0.37,-0.37],[-0.26,-0.45],[-0.14,-0.5],[0,-0.52],[0,-6.67],[0.06,-6.67],[0.14,-0.5],[0.26,-0.45],[0.37,-0.37],[0.45,-0.26],[0.5,-0.14],[0.52,0],[6.67,0],[6.67,0.06],[0.5,0.14],[0.45,0.26],[0.37,0.37],[0.26,0.45],[0.14,0.5],[0,0.52],[0,6.67],[-0.06,6.67],[-0.14,0.5],[-0.26,0.45],[-0.37,0.37],[-0.45,0.26],[-0.5,0.14],[-0.52,0],[-6.67,0]],"o":[[0.52,0],[0.5,0.14],[0.45,0.26],[0.37,0.37],[0.26,0.45],[0.14,0.5],[0.06,6.67],[0,6.67],[0,0.52],[-0.14,0.5],[-0.26,0.45],[-0.37,0.37],[-0.45,0.26],[-0.5,0.14],[-6.67,0.06],[-6.67,0],[-0.52,0],[-0.5,-0.14],[-0.45,-0.26],[-0.37,-0.37],[-0.26,-0.45],[-0.14,-0.5],[-0.06,-6.67],[0,-6.67],[0,-0.52],[0.14,-0.5],[0.26,-0.45],[0.37,-0.37],[0.45,-0.26],[0.5,-0.14],[6.67,-0.06],[6.67,0]],"v":[[20,-26],[21.55,-25.8],[23,-25.2],[24.24,-24.24],[25.2,-23],[25.8,-21.55],[26,-20],[26,0],[26,20],[25.8,21.55],[25.2,23],[24.24,24.24],[23,25.2],[21.55,25.8],[20,26],[0,26],[-20,26],[-21.55,25.8],[-23,25.2],[-24.24,24.24],[-25.2,23],[-25.8,21.55],[-26,20],[-26,0],[-26,-20],[-25.8,-21.55],[-25.2,-23],[-24.24,-24.24],[-23,-25.2],[-21.55,-25.8],[-20,-26],[0,-26]],"c":true}]}]}},{"ty":"fl","c":{"a":1,"k":[{"t":11,"s":[0.85,0.73,0.47,1],"o":{"x":[0.42],"y":[0]},"i":{"x":[0.58],"y":[1]}},{"t":18,"s":[0.91,0.8,0.58,1],"o":{"x":[0.42],"y":[0]},"i":{"x":[0.58],"y":[1]}},{"t":28,"s":[0.85,0.73,0.47,1],"o":{"x":[0.42],"y":[0]},"i":{"x":[0.58],"y":[1]}},{"t":34,"s":[0.85,0.73,0.47,1]}]},"o":{"a":0,"k":100},"r":1,"bm":0},{"ty":"st","c":{"a":0,"k":[0.71,0.58,0.35]},"o":{"a":0,"k":100},"w":{"a":0,"k":1.8},"lc":2,"lj":2,"ml":4,"bm":0},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100}}],"nm":"face"}],"ip":0,"op":34,"st":0,"bm":0}],"markers":[]} \ No newline at end of file