chore: init repo

This commit is contained in:
Jesús Pérez 2026-05-04 18:23:52 +01:00
commit 40be733797
Signed by: jesus
GPG key ID: 9F243E355E0BC939
28 changed files with 4459 additions and 0 deletions

77
.gitignore vendored Normal file
View file

@ -0,0 +1,77 @@
CLAUDE.md
.claude
logs
logs-archive
utils/save*sh
.fastembed_cache
presentaciones
COMMIT_MESSAGE.md
.wrks
nushell
nushell-*
*.tar.gz
#*-nushell-plugins.tar.gz
github-com
.coder
target
artifacts/
distribution
.qodo
# enviroment to load on bin/build
.env
# OSX trash
.DS_Store
# Vscode files
.vscode
# Emacs save files
*~
\#*\#
.\#*
# Vim-related files
[._]*.s[a-w][a-z]
[._]s[a-w][a-z]
*.un~
Session.vim
.netrwhist
# cscope-related files
cscope.*
# User cluster configs
.kubeconfig
.tags*
# direnv .envrc files
.envrc
# make-related metadata
/.make/
# Just in time generated data in the source, should never be committed
/test/e2e/generated/bindata.go
# This file used by some vendor repos (e.g. github.com/go-openapi/...) to store secret variables and should not be ignored
!\.drone\.sec
# Godeps workspace
/Godeps/_workspace
/bazel-*
*.pyc
# generated by verify-vendor.sh
vendordiff.patch
.claude/settings.local.json
# Generated SBOM files
SBOM.*.json
*.sbom.json
# UnoCSS build
assets/css/node_modules/
assets/css/pnpm-lock.yaml
crates/ontoref-daemon/public/css/ontoref.css

115
CHANGES.md Normal file
View file

@ -0,0 +1,115 @@
# Changes
Record of accepted architectural decisions and visible surface changes for
`lian-build`. Format follows [Keep a Changelog]; project is pre-1.0 so the
SemVer guarantee is **not** yet in force — schema and CLI shape may break
between minor versions until `0.1.0` is tagged.
Dates reflect the ADR acceptance date or the date the on-disk artefact was
recorded; this project does not yet publish git tags or release builds.
[Keep a Changelog]: https://keepachangelog.com/en/1.1.0/
---
## [Unreleased] — 0.1.0
### Architecture
- **adr-003 — Nickel coupled via subprocess, not library linkage** *(2026-05-03)*
All NCL → Rust conversions flow through `directives::nickel_export(path,
&[import_paths])`, which spawns `nickel export --format json` and feeds
serde. No `nickel-lang*` crate is added to `Cargo.toml`. `nickel` is a
runtime requirement on `$PATH`.
- **adr-002 — CLI surface is subcommand-only with stdout reserved for
structured envelopes** *(2026-05-03)*
`lian-build` now exposes `{build, integrate}` as clap subcommands. There is
no default subcommand and no flat-arg compatibility shim. `tracing_subscriber`
is pinned to `with_writer(std::io::stderr)` so stdout carries at most one
envelope per invocation. **Breaking** for any caller invoking the legacy
`buildkit-launcher` flat-arg form: migrate to `lian-build build <args>`.
- **adr-001 — lian-build is a standalone build substrate, not part of
provisioning** *(2026-05-01)*
`buildkit-launcher` and `buildkit_runner` extracted from
`provisioning/platform/crates/buildkit-launcher` and
`provisioning/extensions/components/buildkit_runner` into this standalone
project. Binary renamed from `buildkit-launcher` to `lian-build`.
### Added
- `lian-build build` subcommand. Accepts flat args **or** `--directives
<file.ncl>`; directives take precedence over flat-arg fields when both are
present (`src/main.rs::resolve_build_plan`).
- `lian-build integrate` subcommand. Federated probe that reads a
`SecretDeliveryContext` JSON envelope from stdin and emits a single
`ResultEnvelope` JSON line on stdout (`src/integration/`).
- `BuildDirectives` NCL vocabulary (`schemas/build_directives.ncl`) and
matching Rust types in `src/directives.rs` with serde round-trip tests.
- `BuildSpec` schema (`schemas/build_spec.ncl`) with `bounded_cpu_` ≤ 256 and
`bounded_time_budget_` ≤ 1440 min contract bounds; honoured by
`sizing::resolve` at the explicit-spec tier.
- `CachePolicy` schema (`schemas/cache_policy.ncl`) with `BuildMode` =
`'ci | 'session`, `SessionActor` (`'human | 'agent | 'ci_aux`), and
`SessionCacheDisposition` (`'export | 'discard | 'rollback`).
- `src/cache.rs``build_cache_flags(workspace, mode, registry)` returning
import/export `Vec<String>`; enforces `ci/<workspace>/*` (canonical,
read-only to sessions) and `dev/<actor-id>-<workspace>/*` (per-session).
Invariants guarded by unit tests: `ci_never_imports_dev`,
`session_never_exports_ci`, `session_imports_both_layers`,
`mode_switch_purity`.
- `src/sizing.rs` — three-tier resolution: explicit `.build-spec.ncl`
P95 historical (×1.2, floored) → language defaults (rust=4/8/60,
go=2/4/30, java|kotlin|scala=4/8/45, default=2/4/30).
- `src/retry.rs``MAX_OOM_RETRIES = 1` (hard bound), `SIZE_TIERS` walk
`cx22 → cx32 → cx42 → cx52`. OOM is detected via exit-code `137` or stderr
matching `OOM` / `Killed`.
- `src/orchestrator_client.rs` — HTTP client wrapping responses in
`ApiResponse<T> { success, data, error }`. Methods: `spawn_runner`,
`destroy_runner`, `get_p95`, `record_metrics`.
- `src/buildctl_runner.rs``rsync_context` and `run_buildctl` over SSH.
- `src/nats_events.rs``BuildEventPublisher` over
`platform_nats::EventStream`. Publishes `started` / `completed` / `failed`
to `<prefix>.<workspace>.build.<event>`. Connect failures warn and disable
publishing; they never fail a build.
- `defaults/build_directives.ncl``make_*` constructors and
`ci_cache_policy` / `session_cache_policy` helpers.
- `examples/sample.ncl` — example `BuildDirectives` instance.
- Ontoref scaffolding: `.ontology/{core,state,gate,manifest}.ncl`,
`.ontoref/project.ncl`, `card.ncl`, `reflection/{backlog,qa}.ncl`,
`reflection/modes/`, `catalog/{domains,modes}/`.
- Repository assets: `assets/` (logo and branding), `justfile` with imports
from `justfiles/{build,test,ci}.just`, `.pre-commit-config.yaml`.
### Changed
- Binary target renamed `buildkit-launcher``lian-build`. The
`tracing` env-filter directive remains the legacy crate name
(`buildkit_launcher=info`) — explicitly noted in `CLAUDE.md`.
- `--platforms` is now a comma-separated list (multi-platform builds);
legacy single-value invocations remain valid.
### Constraints (grep-checked, ADR-001)
- `no-provisioning-lib-import``Cargo.toml` and `src/` must not match
`platform-config|provisioning|stratum-`. The `platform-nats` path-dep from
`stratumiops` is explicitly allowed (it is shared infrastructure, not the
provisioning workspace).
- `build-directives-ncl-vocabulary``src/` must not match
`provisioning_workspace|vapora_|woodpecker_`.
### Status (`.ontology/state.ncl`)
| Dimension | Current | Desired |
|------------------------------------|--------------------------|---------------------------|
| provider-pluggability-maturity | hcloud-zot-only | provider-trait-stable |
| session-multi-actor-maturity | ci-single-actor | multi-actor-stable |
| active-active-registry-maturity | single-zot-libre-wuji | active-active-verified |
| caller-integration-maturity | provisioning-only | multi-caller-stable |
| peer-publishing-status | packaged | production |
`ComputeProvider` / `RegistryProvider` Rust traits are **not yet declared**;
the orchestrator client is currently hcloud-shaped. Compute is selected via
NCL discriminant but dispatch in core is monolithic — see ADR-001 and the
`provider-pluggability-maturity` dimension.

244
README.md Normal file
View file

@ -0,0 +1,244 @@
<p align="">
<img src="assets/lian-h-static.svg" alt="ontoref" width="300" />
</p>
<br>
# lian-build
> 炼 — *alchemical refinement.* Standalone build substrate for ephemeral BuildKit sessions.
`lian-build` is a single Rust binary that orchestrates ephemeral remote BuildKit
runs against a pluggable orchestrator.
Callers (provisioning, vapora, workspace CI) supply intent as `BuildDirectives` in NCL; `lian-build` controls compute
provisioning, OCI cache flow, and multi-actor session namespacing.
Compute (`hcloud` / `proxmox` / `docker-local`) and registry (`zot` / `harbor` / `ghcr`) are plug-in slots.
| | |
|---|---|
| Crate | `lian-build` (binary), `0.1.0` |
| Status | Beta · pre-1.0, schema and CLI surface still mobile |
| Edition | 2021 |
| ADRs | adr-001 lift-out · adr-002 CLI subcommand discipline · adr-003 Nickel via subprocess |
---
## What it is
- An **orchestrator client**, not a buildkitd. It spawns runners through an
external HTTP orchestrator (default `http://localhost:9011`), `rsync`s the
build context, then drives `buildctl` over SSH.
- A **directives consumer**. Schemas live in `schemas/*.ncl`; the Rust types
in `src/directives.rs` mirror them and round-trip via `serde_json`.
- An **event publisher**. `started` / `completed` / `failed` lifecycle events
go to NATS at `<prefix>.<workspace>.build.<event>` (best-effort — NATS
failures never fail a build).
## What it is not
- Not a library. There is no `lib.rs`. Public surface is the CLI and the NCL
schemas.
- Not coupled to provisioning. ADR-001 forbids importing `provisioning`,
`platform-config`, or any `stratum-`-prefixed crate (grep-checked).
- Not a Nickel runtime. NCL is parsed by shelling out to the `nickel` CLI
(ADR-003). `nickel` must be on `$PATH`.
---
## CLI
Two subcommands, no default, no flat-arg fallback (ADR-002). All logs go to
**stderr**; **stdout** is reserved for one structured envelope per invocation.
### `lian-build build`
Run a build on an ephemeral runner. Flat args **or** a directives file:
```bash
lian-build build \
--workspace <name> \
--context <dir> \
--image <fully-qualified-ref> \
--ssh-key <path> \
[--directives <file.ncl>] \
[--dockerfile Dockerfile] \
[--cache-from <ref>] [--cache-to <ref>] \
[--language rust|go|java|...] \
[--platforms linux/amd64,linux/arm64] \
[--runner-image <id>] \
[--orchestrator-url <url>] \
[--nats-url <url>] [--nats-nkey-seed <seed>] [--nats-subject-prefix <p>]
```
When `--directives <file.ncl>` is supplied it takes precedence over flat-arg
fields except `--ssh-key`, which is still required separately (directives
don't yet carry an inline SSH reference).
Environment fallbacks: `BUILDKIT_WORKSPACE`, `BUILDKIT_SSH_KEY`,
`BUILDKIT_RUNNER_IMAGE`, `ORCHESTRATOR_URL`, `NATS_URL`, `NATS_NKEY_SEED`,
`NATS_SUBJECT_PREFIX`.
### `lian-build integrate`
Federated probe. Reads a `SecretDeliveryContext` JSON envelope from stdin,
emits a `ResultEnvelope` JSON line on stdout, optionally publishes a
completion event to NATS.
```bash
echo '<context-json>' | lian-build integrate \
[--nats-url <url>] [--nats-nkey-seed <seed>]
```
Omit `--nats-url` to skip event emission with a warning (the envelope still
goes to stdout).
---
## Three-tier runner sizing
`sizing::resolve` walks three sources, first match wins:
1. **Explicit**`.build-spec.ncl` in the build context, validated against
`schemas/build_spec.ncl` (`bounded_cpu_` ≤ 256, `bounded_time_budget_`
1440 min).
2. **P95 historical**`OrchestratorClient::get_p95(workspace)` returns
measured CPU / memory P95 from prior runs; multiplied by 1.2, floored at
`min(2 cpu, 4 GB)`.
3. **Language defaults**`RunnerSize::language_default(lang)`:
| Language | CPU | Memory | Time budget |
|------------------------|-----|--------|-------------|
| `rust` | 4 | 8 GB | 60 min |
| `go` | 2 | 4 GB | 30 min |
| `java` / `kotlin` / `scala` | 4 | 8 GB | 45 min |
| *(default)* | 2 | 4 GB | 30 min |
On exit-code `137` or stderr containing `OOM` / `Killed`, the build retries
**once** at the next tier (`cx22``cx32``cx42``cx52`). The retry cap
is hard-bound (`retry::MAX_OOM_RETRIES = 1`).
---
## Cache namespacing
Two layers, defined in `schemas/cache_policy.ncl` and enforced in `src/cache.rs`:
- `ci/<workspace>/*` — canonical, written by CI, **read-only** to sessions.
- `dev/<actor-id>-<workspace>/*` — ephemeral per-session actor.
Sessions read from both layers; CI never imports from `dev/*`. These
invariants are guarded by tests under `src/cache.rs`.
---
## NCL contract surface
| Schema | Defines |
|-------------------------------|-------------------------------------------------------------------------|
| `schemas/build_directives.ncl`| `BuildDirectives`, `BuildArtifact`, `ComputeProviderRef`, `RegistryProviderRef`, `RunnerOverride`, `NatsEventConfig` |
| `schemas/build_spec.ncl` | `BuildSpec` (per-repo `.build-spec.ncl`) |
| `schemas/cache_policy.ncl` | `CachePolicy`, `BuildMode`, `SessionActor`, `SessionCacheDisposition` |
| `schemas/build_result.ncl` | `BuildResult` envelope shape |
| `schemas/vault_refs.ncl` | `VaultCredRef`, `VaultKeyRef` |
| `defaults/build_directives.ncl` | `make_*` constructors, `ci_cache_policy` / `session_cache_policy` helpers |
Validate with the `nickel` CLI:
```bash
nickel export \
--import-path /Users/Akasha/Development/lian-build \
--import-path /Users/Akasha/Development/lian-build/schemas \
--import-path /Users/Akasha/Development/ontoref \
--import-path /Users/Akasha/Development/ontoref/ontology \
schemas/build_directives.ncl
```
---
## Hard constraints (ADR-001)
These two rules are grep-checked and define the lift-out boundary:
1. **`no-provisioning-lib-import`** — `Cargo.toml` and `src/` must not match
`platform-config|provisioning|stratum-`. The `platform-nats` path-dep from
`stratumiops` is explicitly allowed; the constraint targets the
`provisioning` workspace and `stratum-`-prefixed crates.
2. **`build-directives-ncl-vocabulary`** — `src/` must not match
`provisioning_workspace|vapora_|woodpecker_`. Caller-specific logic stays
in caller-supplied directives, not in core.
---
## Build / test / run
```bash
cargo build # debug
cargo build --release # release at target/release/lian-build
cargo clippy -- -D warnings # mandatory before commit
cargo fmt
cargo test # full suite
cargo test <name> # single test by substring
cargo test -- --nocapture # show tracing in tests
```
`just` recipes live in `justfiles/{build,test,ci}.just`. Run `just` (or
`just help`) to list them.
### Stratumiops peer dependency
`platform-nats` is consumed as a local path dependency from
`/Users/Akasha/Development/stratumiops/crates/platform-nats` (declared
directly in `Cargo.toml`). That path must exist for `cargo build` to
succeed — there is no feature flag to disable it.
---
## Module layout
```
src/
main.rs # CLI parsing, top-level orchestration, OOM-retry control flow
buildctl_runner.rs # rsync_context + run_buildctl over SSH; OOM_EXIT_CODE = 137
cache.rs # BuildMode, build_cache_flags, ci/* vs dev/<actor>/* invariants
directives.rs # BuildDirectives ↔ JSON via `nickel export` subprocess
integration/ # federated probe handler (stdin → ResultEnvelope on stdout)
context.rs · event.rs · handler.rs · result.rs · mod.rs
nats_events.rs # BuildEventPublisher over platform-nats::EventStream
orchestrator_client.rs # HTTP client: spawn_runner, destroy_runner, get_p95, record_metrics
retry.rs # MAX_OOM_RETRIES = 1; SIZE_TIERS walk (cx22→cx32→cx42→cx52)
sizing.rs # three-tier resolution
```
Operational surface:
```
adrs/ # accepted ADRs (NCL)
.ontology/ # core, state, gate, manifest — design intent
reflection/ # modes, backlog, qa
schemas/ # caller-facing NCL contracts
defaults/ # constructor / helper NCL
catalog/{domains,modes}/ # federated peer publishing layout
examples/sample.ncl # example BuildDirectives instance
tests/fixtures/ # integration test fixtures
.coder/ # session interaction files (not product docs)
.claude/ # operational config (symlinks into shared dev-system)
```
---
## Further reading
- `adrs/adr-001-lian-build-as-standalone.ncl` — why this project exists,
alternatives rejected, the two grep-checked invariants.
- `adrs/adr-002-cli-subcommand-discipline.ncl` — subcommand-only surface,
stderr/stdout discipline.
- `adrs/adr-003-nickel-via-subprocess.ncl` — why `nickel` is on `$PATH`,
not in `Cargo.toml`.
- `.ontology/core.ncl` — axioms (ephemeral-builds, provider-pluggability,
cache-content-addressed, caller-supplies-directives), tensions, practices.
- `.ontology/state.ncl` — five maturity dimensions and their current
transitions (provider-pluggability, session-multi-actor,
active-active-registry, caller-integration, peer-publishing).
- `CHANGES.md` — record of accepted decisions and visible surface changes.

1062
assets/branding/index.html Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="28 19 110 50" preserveAspectRatio="xMidYMid meet" role="img" aria-label="Lian Build">
<title>Lian Build</title>
<defs>
<linearGradient id="ldh-fg" x1="0" y1="1" x2="0" y2="0">
<stop offset="0" stop-color="#e86c2f"/>
<stop offset="1" stop-color="#f5a623"/>
</linearGradient>
<linearGradient id="ldh-cg" x1="0" y1="1" x2="0" y2="0">
<stop offset="0" stop-color="#f5a623"/>
<stop offset="1" stop-color="#fcd99e"/>
</linearGradient>
<style><![CDATA[
@import url('https://fonts.googleapis.com/css2?family=Jost:wght@400;500&display=swap');
.ldh-flame{fill:url(#ldh-fg);opacity:0;transform-origin:50% 100%;transform-box:fill-box;animation:ldh-flame 23s cubic-bezier(.4,0,.2,1) infinite}
.ldh-core{fill:url(#ldh-cg);opacity:0;transform-origin:50% 100%;transform-box:fill-box;animation:ldh-core 23s cubic-bezier(.4,0,.2,1) infinite}
.ldh-cruc{fill:none;stroke:#c0c0b8;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:34;stroke-dashoffset:34;animation:ldh-cruc 23s cubic-bezier(.4,0,.2,1) infinite}
.ldh-word{fill:#d4a870;font-family:"Jost","Avenir Next",Futura,"Century Gothic","Helvetica Neue",system-ui,sans-serif;font-weight:500;letter-spacing:.22em;font-size:22px;opacity:0;animation:ldh-word 23s cubic-bezier(.4,0,.2,1) infinite}
.ldh-sub{fill:#fbb555;font-family:"Jost","Avenir Next",Futura,"Century Gothic","Helvetica Neue",system-ui,sans-serif;font-weight:500;letter-spacing:.22em;font-size:14px;opacity:0;animation:ldh-sub 23s cubic-bezier(.4,0,.2,1) infinite}
.ldh-fbord{fill:none;stroke:#f07830;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;opacity:0;animation:ldh-fbord 23s cubic-bezier(.4,0,.2,1) infinite}
.ldh-code{opacity:0;animation:ldh-code 23s cubic-bezier(.4,0,.2,1) infinite}
@keyframes ldh-core{
0%{opacity:0;transform:scale(.5)} 1.3%{opacity:.75;transform:scale(1.05)}
3.5%{opacity:.55;transform:scale(.95)} 5.2%{opacity:.35;transform:scale(.92)}
22%{opacity:.55;transform:scale(1)} 37%{opacity:.24;transform:scale(.88)}
53%{opacity:.55;transform:scale(1)} 68%{opacity:.24;transform:scale(.88)}
83%{opacity:.55;transform:scale(1)} 99%{opacity:.35;transform:scale(.92)} 100%{opacity:0}
}
@keyframes ldh-flame{
0%,6.5%{opacity:0;transform:scale(.4)} 8.7%{opacity:.9;transform:scale(1)}
10.4%{opacity:1;transform:scale(1.05)} 12%{opacity:1;transform:scale(1)}
99%{opacity:1;transform:scale(1)} 100%{opacity:0;transform:scale(.4)}
}
@keyframes ldh-fbord{
0%,11.7%{opacity:0} 14.3%{opacity:1} 99%{opacity:1} 100%{opacity:0}
}
@keyframes ldh-code{
0%,15.2%{opacity:0} 17.4%{opacity:1} 99%{opacity:1} 100%{opacity:0}
}
@keyframes ldh-cruc{
0%,18.3%{stroke-dashoffset:34;opacity:0} 18.8%{opacity:.8}
22.6%{stroke-dashoffset:0;opacity:.8} 99%{stroke-dashoffset:0;opacity:.8}
100%{stroke-dashoffset:34;opacity:0}
}
@keyframes ldh-sub{
0%,19.6%{opacity:0} 23.9%{opacity:.85} 99%{opacity:.85} 100%{opacity:0}
}
@keyframes ldh-word{
0%,20.4%{opacity:0} 25.2%{opacity:1} 99%{opacity:1} 100%{opacity:0}
}
@media (prefers-reduced-motion:reduce){
.ldh-flame,.ldh-fbord,.ldh-core,.ldh-cruc,.ldh-word,.ldh-sub,.ldh-code{animation:none}
.ldh-flame{opacity:1} .ldh-fbord{opacity:1} .ldh-code{opacity:1}
.ldh-core{opacity:.35;transform:scale(.95);transform-origin:50% 100%;transform-box:fill-box}
.ldh-cruc{opacity:.8;stroke-dashoffset:0} .ldh-word{opacity:1} .ldh-sub{opacity:.85}
}
]]></style>
</defs>
<rect width="163" height="90" fill="#0d1117"/>
<path class="ldh-cruc" d="M 33 65 L 67 65"/>
<path class="ldh-flame" d="M 50 62 C 33 57 33 37 50 23 C 67 37 67 57 50 62 Z"/>
<path class="ldh-fbord" d="M 50 62 C 33 57 33 37 50 23 C 67 37 67 57 50 62 Z"/>
<path class="ldh-core" d="M 50 57 C 41 52 41 40 50 32 C 59 40 59 52 50 57 Z"/>
<g class="ldh-code" fill="none" stroke="#d4642a" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<polyline points="46,42 43,46 46,50"/>
<line x1="51" y1="43" x2="48" y2="49"/>
<polyline points="54,42 57,46 54,50"/>
</g>
<text class="ldh-word" x="80" y="46" text-anchor="start">lian</text>
<text class="ldh-sub" x="83" y="64" text-anchor="start">build</text>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View file

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="85 19 56 100" preserveAspectRatio="xMidYMid meet" role="img" aria-label="Lian Build">
<title>Lian Build</title>
<defs>
<linearGradient id="ldv-fg" x1="0" y1="1" x2="0" y2="0">
<stop offset="0" stop-color="#e86c2f"/>
<stop offset="1" stop-color="#f5a623"/>
</linearGradient>
<linearGradient id="ldv-cg" x1="0" y1="1" x2="0" y2="0">
<stop offset="0" stop-color="#f5a623"/>
<stop offset="1" stop-color="#fcd99e"/>
</linearGradient>
<style><![CDATA[
@import url('https://fonts.googleapis.com/css2?family=Jost:wght@400;500&display=swap');
.ldv-flame{fill:url(#ldv-fg);opacity:0;transform-origin:50% 100%;transform-box:fill-box;animation:ldv-flame 23s cubic-bezier(.4,0,.2,1) infinite}
.ldv-core{fill:url(#ldv-cg);opacity:0;transform-origin:50% 100%;transform-box:fill-box;animation:ldv-core 23s cubic-bezier(.4,0,.2,1) infinite}
.ldv-cruc{fill:none;stroke:#c0c0b8;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:36;stroke-dashoffset:36;animation:ldv-cruc 23s cubic-bezier(.4,0,.2,1) infinite}
.ldv-word{fill:#d4a870;font-family:"Jost","Avenir Next",Futura,"Century Gothic","Helvetica Neue",system-ui,sans-serif;font-weight:500;letter-spacing:.22em;font-size:22px;opacity:0;animation:ldv-word 23s cubic-bezier(.4,0,.2,1) infinite}
.ldv-sub{fill:#fbb555;font-family:"Jost","Avenir Next",Futura,"Century Gothic","Helvetica Neue",system-ui,sans-serif;font-weight:500;letter-spacing:.22em;font-size:14px;opacity:0;animation:ldv-sub 23s cubic-bezier(.4,0,.2,1) infinite}
.ldv-fbord{fill:none;stroke:#f07830;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;opacity:0;animation:ldv-fbord 23s cubic-bezier(.4,0,.2,1) infinite}
.ldv-code{opacity:0;animation:ldv-code 23s cubic-bezier(.4,0,.2,1) infinite}
@keyframes ldv-core{
0%{opacity:0;transform:scale(.5)} 1.3%{opacity:.75;transform:scale(1.05)}
3.5%{opacity:.55;transform:scale(.95)} 5.2%{opacity:.35;transform:scale(.92)}
22%{opacity:.55;transform:scale(1)} 37%{opacity:.24;transform:scale(.88)}
53%{opacity:.55;transform:scale(1)} 68%{opacity:.24;transform:scale(.88)}
83%{opacity:.55;transform:scale(1)} 99%{opacity:.35;transform:scale(.92)} 100%{opacity:0}
}
@keyframes ldv-flame{
0%,6.5%{opacity:0;transform:scale(.4)} 8.7%{opacity:.9;transform:scale(1)}
10.4%{opacity:1;transform:scale(1.05)} 12%{opacity:1;transform:scale(1)}
99%{opacity:1;transform:scale(1)} 100%{opacity:0;transform:scale(.4)}
}
@keyframes ldv-fbord{
0%,11.7%{opacity:0} 14.3%{opacity:1} 99%{opacity:1} 100%{opacity:0}
}
@keyframes ldv-code{
0%,15.2%{opacity:0} 17.4%{opacity:1} 99%{opacity:1} 100%{opacity:0}
}
@keyframes ldv-cruc{
0%,18.3%{stroke-dashoffset:36;opacity:0} 18.8%{opacity:.8}
22.6%{stroke-dashoffset:0;opacity:.8} 99%{stroke-dashoffset:0;opacity:.8}
100%{stroke-dashoffset:36;opacity:0}
}
@keyframes ldv-sub{
0%,19.6%{opacity:0} 23.9%{opacity:.85} 99%{opacity:.85} 100%{opacity:0}
}
@keyframes ldv-word{
0%,20.4%{opacity:0} 25.2%{opacity:1} 99%{opacity:1} 100%{opacity:0}
}
@media (prefers-reduced-motion:reduce){
.ldv-flame,.ldv-fbord,.ldv-core,.ldv-cruc,.ldv-word,.ldv-sub,.ldv-code{animation:none}
.ldv-flame{opacity:1} .ldv-fbord{opacity:1} .ldv-code{opacity:1}
.ldv-core{opacity:.35;transform:scale(.95);transform-origin:50% 100%;transform-box:fill-box}
.ldv-cruc{opacity:.8;stroke-dashoffset:0} .ldv-word{opacity:1} .ldv-sub{opacity:.85}
}
]]></style>
</defs>
<rect x="55" width="110" height="160" fill="#0d1117"/>
<path class="ldv-cruc" d="M 92 66 L 128 66"/>
<path class="ldv-flame" d="M 110 64 C 92 57 92 36 110 22 C 128 36 128 57 110 64 Z"/>
<path class="ldv-fbord" d="M 110 64 C 92 57 92 36 110 22 C 128 36 128 57 110 64 Z"/>
<path class="ldv-core" d="M 110 58 C 100 52 100 40 110 32 C 120 40 120 52 110 58 Z"/>
<g class="ldv-code" fill="none" stroke="#d4642a" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<polyline points="106,42 103,46 106,50"/>
<line x1="112" y1="43" x2="109" y2="49"/>
<polyline points="115,42 118,46 115,50"/>
</g>
<text class="ldv-word" x="113" y="93" text-anchor="middle">lian</text>
<text class="ldv-sub" x="112" y="110" text-anchor="middle">build</text>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="28 19 110 50" preserveAspectRatio="xMidYMid meet" role="img" aria-label="Lian Build">
<title>Lian Build</title>
<defs>
<linearGradient id="lhs-fg" x1="0" y1="1" x2="0" y2="0">
<stop offset="0" stop-color="#e86c2f"/>
<stop offset="1" stop-color="#f5a623"/>
</linearGradient>
<linearGradient id="lhs-cg" x1="0" y1="1" x2="0" y2="0">
<stop offset="0" stop-color="#f5a623"/>
<stop offset="1" stop-color="#fcd99e"/>
</linearGradient>
<style><![CDATA[
@import url('https://fonts.googleapis.com/css2?family=Jost:wght@400;500&display=swap');
.lhs-word,.lhs-sub{font-family:"Jost","Avenir Next",Futura,"Century Gothic","Helvetica Neue",system-ui,sans-serif;font-weight:500;letter-spacing:.22em}
]]></style>
</defs>
<path fill="none" stroke="#a8a8a0" stroke-width="1.5" stroke-linecap="round" d="M 33 65 L 67 65" opacity=".8"/>
<path fill="url(#lhs-fg)" d="M 50 62 C 33 57 33 37 50 23 C 67 37 67 57 50 62 Z"/>
<path fill="none" stroke="#e86c2f" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" d="M 50 62 C 33 57 33 37 50 23 C 67 37 67 57 50 62 Z"/>
<path fill="url(#lhs-cg)" d="M 50 57 C 41 52 41 40 50 32 C 59 40 59 52 50 57 Z" opacity=".35" transform="translate(50,57) scale(.92) translate(-50,-57)"/>
<g fill="none" stroke="#c25a1a" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<polyline points="46,42 43,46 46,50"/>
<line x1="51" y1="43" x2="48" y2="49"/>
<polyline points="54,42 57,46 54,50"/>
</g>
<text class="lhs-word" fill="#bd9156" font-size="22" x="80" y="46" text-anchor="start">lian</text>
<text class="lhs-sub" fill="#f29a3d" font-size="14" x="83" y="64" text-anchor="start" opacity=".85">build</text>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="28 19 110 50" preserveAspectRatio="xMidYMid meet" role="img" aria-label="Lian Build">
<title>Lian Build</title>
<defs>
<linearGradient id="lh-fg" x1="0" y1="1" x2="0" y2="0">
<stop offset="0" stop-color="#e86c2f"/>
<stop offset="1" stop-color="#f5a623"/>
</linearGradient>
<linearGradient id="lh-cg" x1="0" y1="1" x2="0" y2="0">
<stop offset="0" stop-color="#f5a623"/>
<stop offset="1" stop-color="#fcd99e"/>
</linearGradient>
<style><![CDATA[
@import url('https://fonts.googleapis.com/css2?family=Jost:wght@400;500&display=swap');
.lh-flame{fill:url(#lh-fg);opacity:0;transform-origin:50% 100%;transform-box:fill-box;animation:lh-flame 23s cubic-bezier(.4,0,.2,1) infinite}
.lh-core{fill:url(#lh-cg);opacity:0;transform-origin:50% 100%;transform-box:fill-box;animation:lh-core 23s cubic-bezier(.4,0,.2,1) infinite}
.lh-cruc{fill:none;stroke:#a8a8a0;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:34;stroke-dashoffset:34;animation:lh-cruc 23s cubic-bezier(.4,0,.2,1) infinite}
.lh-word{fill:#bd9156;font-family:"Jost","Avenir Next",Futura,"Century Gothic","Helvetica Neue",system-ui,sans-serif;font-weight:500;letter-spacing:.22em;font-size:22px;opacity:0;animation:lh-word 23s cubic-bezier(.4,0,.2,1) infinite}
.lh-sub{fill:#f29a3d;font-family:"Jost","Avenir Next",Futura,"Century Gothic","Helvetica Neue",system-ui,sans-serif;font-weight:500;letter-spacing:.22em;font-size:14px;opacity:0;animation:lh-sub 23s cubic-bezier(.4,0,.2,1) infinite}
.lh-fbord{fill:none;stroke:#e86c2f;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;opacity:0;animation:lh-fbord 23s cubic-bezier(.4,0,.2,1) infinite}
.lh-code{opacity:0;animation:lh-code 23s cubic-bezier(.4,0,.2,1) infinite}
@keyframes lh-core{
0%{opacity:0;transform:scale(.5)} 1.3%{opacity:.75;transform:scale(1.05)}
3.5%{opacity:.55;transform:scale(.95)} 5.2%{opacity:.35;transform:scale(.92)}
22%{opacity:.55;transform:scale(1)} 37%{opacity:.24;transform:scale(.88)}
53%{opacity:.55;transform:scale(1)} 68%{opacity:.24;transform:scale(.88)}
83%{opacity:.55;transform:scale(1)} 99%{opacity:.35;transform:scale(.92)} 100%{opacity:0}
}
@keyframes lh-flame{
0%,6.5%{opacity:0;transform:scale(.4)} 8.7%{opacity:.9;transform:scale(1)}
10.4%{opacity:1;transform:scale(1.05)} 12%{opacity:1;transform:scale(1)}
99%{opacity:1;transform:scale(1)} 100%{opacity:0;transform:scale(.4)}
}
@keyframes lh-fbord{
0%,11.7%{opacity:0} 14.3%{opacity:1} 99%{opacity:1} 100%{opacity:0}
}
@keyframes lh-code{
0%,15.2%{opacity:0} 17.4%{opacity:1} 99%{opacity:1} 100%{opacity:0}
}
@keyframes lh-cruc{
0%,18.3%{stroke-dashoffset:34;opacity:0} 18.8%{opacity:.8}
22.6%{stroke-dashoffset:0;opacity:.8} 99%{stroke-dashoffset:0;opacity:.8}
100%{stroke-dashoffset:34;opacity:0}
}
@keyframes lh-sub{
0%,19.6%{opacity:0} 23.9%{opacity:.85} 99%{opacity:.85} 100%{opacity:0}
}
@keyframes lh-word{
0%,20.4%{opacity:0} 25.2%{opacity:1} 99%{opacity:1} 100%{opacity:0}
}
@media (prefers-reduced-motion:reduce){
.lh-flame,.lh-fbord,.lh-core,.lh-cruc,.lh-word,.lh-sub,.lh-code{animation:none}
.lh-flame{opacity:1} .lh-fbord{opacity:1} .lh-code{opacity:1}
.lh-core{opacity:.35;transform:scale(.95);transform-origin:50% 100%;transform-box:fill-box}
.lh-cruc{opacity:.8;stroke-dashoffset:0} .lh-word{opacity:1} .lh-sub{opacity:.85}
}
]]></style>
</defs>
<path class="lh-cruc" d="M 33 65 L 67 65"/>
<path class="lh-flame" d="M 50 62 C 33 57 33 37 50 23 C 67 37 67 57 50 62 Z"/>
<path class="lh-fbord" d="M 50 62 C 33 57 33 37 50 23 C 67 37 67 57 50 62 Z"/>
<path class="lh-core" d="M 50 57 C 41 52 41 40 50 32 C 59 40 59 52 50 57 Z"/>
<g class="lh-code" fill="none" stroke="#c25a1a" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<polyline points="46,42 43,46 46,50"/>
<line x1="51" y1="43" x2="48" y2="49"/>
<polyline points="54,42 57,46 54,50"/>
</g>
<text class="lh-word" x="80" y="46" text-anchor="start">lian</text>
<text class="lh-sub" x="83" y="64" text-anchor="start">build</text>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="30 20 40 48" preserveAspectRatio="xMidYMid meet" role="img" aria-label="Lian Build icon">
<title>Lian Build icon</title>
<defs>
<linearGradient id="lis-fg" x1="0" y1="1" x2="0" y2="0">
<stop offset="0" stop-color="#e86c2f"/>
<stop offset="1" stop-color="#f5a623"/>
</linearGradient>
<linearGradient id="lis-cg" x1="0" y1="1" x2="0" y2="0">
<stop offset="0" stop-color="#f5a623"/>
<stop offset="1" stop-color="#fcd99e"/>
</linearGradient>
</defs>
<path fill="none" stroke="#a8a8a0" stroke-width="1.5" stroke-linecap="round" d="M 33 65 L 67 65" opacity=".8"/>
<path fill="url(#lis-fg)" d="M 50 62 C 33 57 33 37 50 23 C 67 37 67 57 50 62 Z"/>
<path fill="none" stroke="#e86c2f" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" d="M 50 62 C 33 57 33 37 50 23 C 67 37 67 57 50 62 Z"/>
<path fill="url(#lis-cg)" d="M 50 57 C 41 52 41 40 50 32 C 59 40 59 52 50 57 Z" opacity=".35" transform="translate(50,57) scale(.92) translate(-50,-57)"/>
<g fill="none" stroke="#c25a1a" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<polyline points="46,42 43,46 46,50"/>
<line x1="51" y1="43" x2="48" y2="49"/>
<polyline points="54,42 57,46 54,50"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="30 20 40 48" preserveAspectRatio="xMidYMid meet" role="img" aria-label="Lian Build icon">
<title>Lian Build icon</title>
<defs>
<linearGradient id="li-fg" x1="0" y1="1" x2="0" y2="0">
<stop offset="0" stop-color="#e86c2f"/>
<stop offset="1" stop-color="#f5a623"/>
</linearGradient>
<linearGradient id="li-cg" x1="0" y1="1" x2="0" y2="0">
<stop offset="0" stop-color="#f5a623"/>
<stop offset="1" stop-color="#fcd99e"/>
</linearGradient>
<style><![CDATA[
.li-flame{fill:url(#li-fg);opacity:0;transform-origin:50% 100%;transform-box:fill-box;animation:li-flame 23s cubic-bezier(.4,0,.2,1) infinite}
.li-core{fill:url(#li-cg);opacity:0;transform-origin:50% 100%;transform-box:fill-box;animation:li-core 23s cubic-bezier(.4,0,.2,1) infinite}
.li-cruc{fill:none;stroke:#a8a8a0;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:34;stroke-dashoffset:34;animation:li-cruc 23s cubic-bezier(.4,0,.2,1) infinite}
.li-fbord{fill:none;stroke:#e86c2f;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;opacity:0;animation:li-fbord 23s cubic-bezier(.4,0,.2,1) infinite}
.li-code{opacity:0;animation:li-code 23s cubic-bezier(.4,0,.2,1) infinite}
@keyframes li-core{
0%{opacity:0;transform:scale(.5)} 1.3%{opacity:.75;transform:scale(1.05)}
3.5%{opacity:.55;transform:scale(.95)} 5.2%{opacity:.35;transform:scale(.92)}
22%{opacity:.55;transform:scale(1)} 37%{opacity:.24;transform:scale(.88)}
53%{opacity:.55;transform:scale(1)} 68%{opacity:.24;transform:scale(.88)}
83%{opacity:.55;transform:scale(1)} 99%{opacity:.35;transform:scale(.92)} 100%{opacity:0}
}
@keyframes li-flame{
0%,6.5%{opacity:0;transform:scale(.4)} 8.7%{opacity:.9;transform:scale(1)}
10.4%{opacity:1;transform:scale(1.05)} 12%{opacity:1;transform:scale(1)}
99%{opacity:1;transform:scale(1)} 100%{opacity:0;transform:scale(.4)}
}
@keyframes li-fbord{
0%,11.7%{opacity:0} 14.3%{opacity:1} 99%{opacity:1} 100%{opacity:0}
}
@keyframes li-code{
0%,15.2%{opacity:0} 17.4%{opacity:1} 99%{opacity:1} 100%{opacity:0}
}
@keyframes li-cruc{
0%,18.3%{stroke-dashoffset:34;opacity:0} 18.8%{opacity:.8}
22.6%{stroke-dashoffset:0;opacity:.8} 99%{stroke-dashoffset:0;opacity:.8}
100%{stroke-dashoffset:34;opacity:0}
}
@media (prefers-reduced-motion:reduce){
.li-flame,.li-fbord,.li-core,.li-cruc,.li-code{animation:none}
.li-flame{opacity:1} .li-fbord{opacity:1} .li-code{opacity:1}
.li-core{opacity:.35;transform:scale(.95);transform-origin:50% 100%;transform-box:fill-box}
.li-cruc{opacity:.8;stroke-dashoffset:0}
}
]]></style>
</defs>
<path class="li-cruc" d="M 33 65 L 67 65"/>
<path class="li-flame" d="M 50 62 C 33 57 33 37 50 23 C 67 37 67 57 50 62 Z"/>
<path class="li-fbord" d="M 50 62 C 33 57 33 37 50 23 C 67 37 67 57 50 62 Z"/>
<path class="li-core" d="M 50 57 C 41 52 41 40 50 32 C 59 40 59 52 50 57 Z"/>
<g class="li-code" fill="none" stroke="#c25a1a" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<polyline points="46,42 43,46 46,50"/>
<line x1="51" y1="43" x2="48" y2="49"/>
<polyline points="54,42 57,46 54,50"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="28 19 110 50" preserveAspectRatio="xMidYMid meet" role="img" aria-label="Lian Build">
<title>Lian Build</title>
<defs>
<style><![CDATA[
@import url('https://fonts.googleapis.com/css2?family=Jost:wght@400;500&display=swap');
.lmbh-word,.lmbh-sub{font-family:"Jost","Avenir Next",Futura,"Century Gothic","Helvetica Neue",system-ui,sans-serif;font-weight:500;letter-spacing:.22em}
]]></style>
</defs>
<path fill="none" stroke="#1a1a1a" stroke-width="1.5" stroke-linecap="round" d="M 33 65 L 67 65" opacity=".75"/>
<path fill="#1a1a1a" d="M 50 62 C 33 57 33 37 50 23 C 67 37 67 57 50 62 Z"/>
<path fill="none" stroke="#1a1a1a" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" d="M 50 62 C 33 57 33 37 50 23 C 67 37 67 57 50 62 Z"/>
<path fill="#5a5a5a" d="M 50 57 C 41 52 41 40 50 32 C 59 40 59 52 50 57 Z"/>
<g fill="none" stroke="#d8d8d8" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<polyline points="46,42 43,46 46,50"/>
<line x1="51" y1="43" x2="48" y2="49"/>
<polyline points="54,42 57,46 54,50"/>
</g>
<text class="lmbh-word" fill="#1a1a1a" font-size="22" x="80" y="46" text-anchor="start">lian</text>
<text class="lmbh-sub" fill="#3d3d3d" font-size="14" x="83" y="64" text-anchor="start" opacity=".85">build</text>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="85 19 56 100" preserveAspectRatio="xMidYMid meet" role="img" aria-label="Lian Build">
<title>Lian Build</title>
<defs>
<style><![CDATA[
@import url('https://fonts.googleapis.com/css2?family=Jost:wght@400;500&display=swap');
.lmbv-word,.lmbv-sub{font-family:"Jost","Avenir Next",Futura,"Century Gothic","Helvetica Neue",system-ui,sans-serif;font-weight:500;letter-spacing:.22em}
]]></style>
</defs>
<path fill="none" stroke="#1a1a1a" stroke-width="1.5" stroke-linecap="round" d="M 92 66 L 128 66" opacity=".75"/>
<path fill="#1a1a1a" d="M 110 64 C 92 57 92 36 110 22 C 128 36 128 57 110 64 Z"/>
<path fill="none" stroke="#1a1a1a" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" d="M 110 64 C 92 57 92 36 110 22 C 128 36 128 57 110 64 Z"/>
<path fill="#5a5a5a" d="M 110 58 C 100 52 100 40 110 32 C 120 40 120 52 110 58 Z"/>
<g fill="none" stroke="#d8d8d8" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<polyline points="106,42 103,46 106,50"/>
<line x1="112" y1="43" x2="109" y2="49"/>
<polyline points="115,42 118,46 115,50"/>
</g>
<text class="lmbv-word" fill="#1a1a1a" font-size="22" x="113" y="93" text-anchor="middle">lian</text>
<text class="lmbv-sub" fill="#3d3d3d" font-size="14" x="112" y="110" text-anchor="middle" opacity=".85">build</text>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="28 19 110 50" preserveAspectRatio="xMidYMid meet" role="img" aria-label="Lian Build">
<title>Lian Build</title>
<defs>
<style><![CDATA[
@import url('https://fonts.googleapis.com/css2?family=Jost:wght@400;500&display=swap');
.lmwh-word,.lmwh-sub{font-family:"Jost","Avenir Next",Futura,"Century Gothic","Helvetica Neue",system-ui,sans-serif;font-weight:500;letter-spacing:.22em}
]]></style>
</defs>
<rect width="163" height="90" fill="#0d1117"/>
<path fill="none" stroke="#d0d0c8" stroke-width="1.5" stroke-linecap="round" d="M 33 65 L 67 65" opacity=".75"/>
<path fill="#e8e8e8" d="M 50 62 C 33 57 33 37 50 23 C 67 37 67 57 50 62 Z"/>
<path fill="none" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" d="M 50 62 C 33 57 33 37 50 23 C 67 37 67 57 50 62 Z"/>
<path fill="#ffffff" d="M 50 57 C 41 52 41 40 50 32 C 59 40 59 52 50 57 Z" opacity=".45"/>
<g fill="none" stroke="#c0c0c0" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<polyline points="46,42 43,46 46,50"/>
<line x1="51" y1="43" x2="48" y2="49"/>
<polyline points="54,42 57,46 54,50"/>
</g>
<text class="lmwh-word" fill="#ffffff" font-size="22" x="80" y="46" text-anchor="start">lian</text>
<text class="lmwh-sub" fill="#c0c0c0" font-size="14" x="83" y="64" text-anchor="start" opacity=".85">build</text>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="85 19 56 100" preserveAspectRatio="xMidYMid meet" role="img" aria-label="Lian Build">
<title>Lian Build</title>
<defs>
<style><![CDATA[
@import url('https://fonts.googleapis.com/css2?family=Jost:wght@400;500&display=swap');
.lmwv-word,.lmwv-sub{font-family:"Jost","Avenir Next",Futura,"Century Gothic","Helvetica Neue",system-ui,sans-serif;font-weight:500;letter-spacing:.22em}
]]></style>
</defs>
<rect x="55" width="110" height="160" fill="#0d1117"/>
<path fill="none" stroke="#d0d0c8" stroke-width="1.5" stroke-linecap="round" d="M 92 66 L 128 66" opacity=".75"/>
<path fill="#e8e8e8" d="M 110 64 C 92 57 92 36 110 22 C 128 36 128 57 110 64 Z"/>
<path fill="none" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" d="M 110 64 C 92 57 92 36 110 22 C 128 36 128 57 110 64 Z"/>
<path fill="#ffffff" d="M 110 58 C 100 52 100 40 110 32 C 120 40 120 52 110 58 Z" opacity=".45"/>
<g fill="none" stroke="#c0c0c0" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<polyline points="106,42 103,46 106,50"/>
<line x1="112" y1="43" x2="109" y2="49"/>
<polyline points="115,42 118,46 115,50"/>
</g>
<text class="lmwv-word" fill="#ffffff" font-size="22" x="113" y="93" text-anchor="middle">lian</text>
<text class="lmwv-sub" fill="#c0c0c0" font-size="14" x="112" y="110" text-anchor="middle" opacity=".85">build</text>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="65 0 110 80" preserveAspectRatio="xMidYMid meet" role="img" aria-label="Lian Build wordmark">
<title>Lian Build wordmark</title>
<defs>
<style><![CDATA[
@import url('https://fonts.googleapis.com/css2?family=Jost:wght@400;500&display=swap');
@keyframes lt-in{0%{opacity:0;transform:translateY(6px)}100%{opacity:1;transform:translateY(0)}}
.lt-w{animation:lt-in .9s ease-out .3s both}
.lt-s{animation:lt-in .9s ease-out .55s both}
]]></style>
</defs>
<text class="lt-w" x="120" y="54" text-anchor="middle"
font-family='"Jost","Avenir Next",Futura,"Century Gothic","Helvetica Neue",system-ui,sans-serif'
font-weight="500" font-size="52" letter-spacing=".1em" fill="#bd9156">lian</text>
<text class="lt-s" x="124" y="74" text-anchor="middle"
font-family='"Jost","Avenir Next",Futura,"Century Gothic","Helvetica Neue",system-ui,sans-serif'
font-weight="500" font-size="20" letter-spacing=".22em" fill="#f29a3d">build</text>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="85 19 56 100" preserveAspectRatio="xMidYMid meet" role="img" aria-label="Lian Build">
<title>Lian Build</title>
<defs>
<linearGradient id="lvs-fg" x1="0" y1="1" x2="0" y2="0">
<stop offset="0" stop-color="#e86c2f"/>
<stop offset="1" stop-color="#f5a623"/>
</linearGradient>
<linearGradient id="lvs-cg" x1="0" y1="1" x2="0" y2="0">
<stop offset="0" stop-color="#f5a623"/>
<stop offset="1" stop-color="#fcd99e"/>
</linearGradient>
<style><![CDATA[
@import url('https://fonts.googleapis.com/css2?family=Jost:wght@400;500&display=swap');
.lvs-word,.lvs-sub{font-family:"Jost","Avenir Next",Futura,"Century Gothic","Helvetica Neue",system-ui,sans-serif;font-weight:500;letter-spacing:.22em}
]]></style>
</defs>
<path fill="none" stroke="#a8a8a0" stroke-width="1.5" stroke-linecap="round" d="M 92 66 L 128 66" opacity=".8"/>
<path fill="url(#lvs-fg)" d="M 110 64 C 92 57 92 36 110 22 C 128 36 128 57 110 64 Z"/>
<path fill="none" stroke="#e86c2f" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" d="M 110 64 C 92 57 92 36 110 22 C 128 36 128 57 110 64 Z"/>
<path fill="url(#lvs-cg)" d="M 110 58 C 100 52 100 40 110 32 C 120 40 120 52 110 58 Z" opacity=".35" transform-origin="110 58" transform="scale(.92)"/>
<g fill="none" stroke="#c25a1a" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<polyline points="106,42 103,46 106,50"/>
<line x1="112" y1="43" x2="109" y2="49"/>
<polyline points="115,42 118,46 115,50"/>
</g>
<text class="lvs-word" fill="#bd9156" font-size="22" x="113" y="93" text-anchor="middle">lian</text>
<text class="lvs-sub" fill="#f29a3d" font-size="14" x="112" y="110" text-anchor="middle" opacity=".85">build</text>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="85 19 56 100" preserveAspectRatio="xMidYMid meet" role="img" aria-label="Lian Build">
<title>Lian Build</title>
<defs>
<linearGradient id="lv-fg" x1="0" y1="1" x2="0" y2="0">
<stop offset="0" stop-color="#e86c2f"/>
<stop offset="1" stop-color="#f5a623"/>
</linearGradient>
<linearGradient id="lv-cg" x1="0" y1="1" x2="0" y2="0">
<stop offset="0" stop-color="#f5a623"/>
<stop offset="1" stop-color="#fcd99e"/>
</linearGradient>
<style><![CDATA[
@import url('https://fonts.googleapis.com/css2?family=Jost:wght@400;500&display=swap');
.lv-flame{fill:url(#lv-fg);opacity:0;transform-origin:50% 100%;transform-box:fill-box;animation:lv-flame 23s cubic-bezier(.4,0,.2,1) infinite}
.lv-core{fill:url(#lv-cg);opacity:0;transform-origin:50% 100%;transform-box:fill-box;animation:lv-core 23s cubic-bezier(.4,0,.2,1) infinite}
.lv-cruc{fill:none;stroke:#a8a8a0;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:36;stroke-dashoffset:36;animation:lv-cruc 23s cubic-bezier(.4,0,.2,1) infinite}
.lv-word{fill:#bd9156;font-family:"Jost","Avenir Next",Futura,"Century Gothic","Helvetica Neue",system-ui,sans-serif;font-weight:500;letter-spacing:.22em;font-size:22px;opacity:0;animation:lv-word 23s cubic-bezier(.4,0,.2,1) infinite}
.lv-sub{fill:#f29a3d;font-family:"Jost","Avenir Next",Futura,"Century Gothic","Helvetica Neue",system-ui,sans-serif;font-weight:500;letter-spacing:.22em;font-size:14px;opacity:0;animation:lv-sub 23s cubic-bezier(.4,0,.2,1) infinite}
.lv-fbord{fill:none;stroke:#e86c2f;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;opacity:0;animation:lv-fbord 23s cubic-bezier(.4,0,.2,1) infinite}
.lv-code{opacity:0;animation:lv-code 23s cubic-bezier(.4,0,.2,1) infinite}
@keyframes lv-core{
0%{opacity:0;transform:scale(.5)} 1.3%{opacity:.75;transform:scale(1.05)}
3.5%{opacity:.55;transform:scale(.95)} 5.2%{opacity:.35;transform:scale(.92)}
22%{opacity:.55;transform:scale(1)} 37%{opacity:.24;transform:scale(.88)}
53%{opacity:.55;transform:scale(1)} 68%{opacity:.24;transform:scale(.88)}
83%{opacity:.55;transform:scale(1)} 99%{opacity:.35;transform:scale(.92)} 100%{opacity:0}
}
@keyframes lv-flame{
0%,6.5%{opacity:0;transform:scale(.4)} 8.7%{opacity:.9;transform:scale(1)}
10.4%{opacity:1;transform:scale(1.05)} 12%{opacity:1;transform:scale(1)}
99%{opacity:1;transform:scale(1)} 100%{opacity:0;transform:scale(.4)}
}
@keyframes lv-fbord{
0%,11.7%{opacity:0} 14.3%{opacity:1} 99%{opacity:1} 100%{opacity:0}
}
@keyframes lv-code{
0%,15.2%{opacity:0} 17.4%{opacity:1} 99%{opacity:1} 100%{opacity:0}
}
@keyframes lv-cruc{
0%,18.3%{stroke-dashoffset:36;opacity:0} 18.8%{opacity:.8}
22.6%{stroke-dashoffset:0;opacity:.8} 99%{stroke-dashoffset:0;opacity:.8}
100%{stroke-dashoffset:36;opacity:0}
}
@keyframes lv-sub{
0%,19.6%{opacity:0} 23.9%{opacity:.85} 99%{opacity:.85} 100%{opacity:0}
}
@keyframes lv-word{
0%,20.4%{opacity:0} 25.2%{opacity:1} 99%{opacity:1} 100%{opacity:0}
}
@media (prefers-reduced-motion:reduce){
.lv-flame,.lv-fbord,.lv-core,.lv-cruc,.lv-word,.lv-sub,.lv-code{animation:none}
.lv-flame{opacity:1} .lv-fbord{opacity:1} .lv-code{opacity:1}
.lv-core{opacity:.35;transform:scale(.95);transform-origin:50% 100%;transform-box:fill-box}
.lv-cruc{opacity:.8;stroke-dashoffset:0} .lv-word{opacity:1} .lv-sub{opacity:.85}
}
]]></style>
</defs>
<path class="lv-cruc" d="M 92 66 L 128 66"/>
<path class="lv-flame" d="M 110 64 C 92 57 92 36 110 22 C 128 36 128 57 110 64 Z"/>
<path class="lv-fbord" d="M 110 64 C 92 57 92 36 110 22 C 128 36 128 57 110 64 Z"/>
<path class="lv-core" d="M 110 58 C 100 52 100 40 110 32 C 120 40 120 52 110 58 Z"/>
<g class="lv-code" fill="none" stroke="#c25a1a" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<polyline points="106,42 103,46 106,50"/>
<line x1="112" y1="43" x2="109" y2="49"/>
<polyline points="115,42 118,46 115,50"/>
</g>
<text class="lv-word" x="113" y="93" text-anchor="middle">lian</text>
<text class="lv-sub" x="112" y="110" text-anchor="middle">build</text>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

29
assets/lian-h-static.svg Normal file
View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="28 19 110 50" preserveAspectRatio="xMidYMid meet" role="img" aria-label="Lian Build">
<title>Lian Build</title>
<defs>
<linearGradient id="lhs-fg" x1="0" y1="1" x2="0" y2="0">
<stop offset="0" stop-color="#e86c2f"/>
<stop offset="1" stop-color="#f5a623"/>
</linearGradient>
<linearGradient id="lhs-cg" x1="0" y1="1" x2="0" y2="0">
<stop offset="0" stop-color="#f5a623"/>
<stop offset="1" stop-color="#fcd99e"/>
</linearGradient>
<style><![CDATA[
@import url('https://fonts.googleapis.com/css2?family=Jost:wght@400;500&display=swap');
.lhs-word,.lhs-sub{font-family:"Jost","Avenir Next",Futura,"Century Gothic","Helvetica Neue",system-ui,sans-serif;font-weight:500;letter-spacing:.22em}
]]></style>
</defs>
<path fill="none" stroke="#a8a8a0" stroke-width="1.5" stroke-linecap="round" d="M 33 65 L 67 65" opacity=".8"/>
<path fill="url(#lhs-fg)" d="M 50 62 C 33 57 33 37 50 23 C 67 37 67 57 50 62 Z"/>
<path fill="none" stroke="#e86c2f" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" d="M 50 62 C 33 57 33 37 50 23 C 67 37 67 57 50 62 Z"/>
<path fill="url(#lhs-cg)" d="M 50 57 C 41 52 41 40 50 32 C 59 40 59 52 50 57 Z" opacity=".35" transform="translate(50,57) scale(.92) translate(-50,-57)"/>
<g fill="none" stroke="#c25a1a" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<polyline points="46,42 43,46 46,50"/>
<line x1="51" y1="43" x2="48" y2="49"/>
<polyline points="54,42 57,46 54,50"/>
</g>
<text class="lhs-word" fill="#bd9156" font-size="22" x="80" y="46" text-anchor="start">lian</text>
<text class="lhs-sub" fill="#f29a3d" font-size="14" x="83" y="64" text-anchor="start" opacity=".85">build</text>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

70
assets/lian-h.svg Normal file
View file

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="28 19 110 50" preserveAspectRatio="xMidYMid meet" role="img" aria-label="Lian Build">
<title>Lian Build</title>
<defs>
<linearGradient id="lh-fg" x1="0" y1="1" x2="0" y2="0">
<stop offset="0" stop-color="#e86c2f"/>
<stop offset="1" stop-color="#f5a623"/>
</linearGradient>
<linearGradient id="lh-cg" x1="0" y1="1" x2="0" y2="0">
<stop offset="0" stop-color="#f5a623"/>
<stop offset="1" stop-color="#fcd99e"/>
</linearGradient>
<style><![CDATA[
@import url('https://fonts.googleapis.com/css2?family=Jost:wght@400;500&display=swap');
.lh-flame{fill:url(#lh-fg);opacity:0;transform-origin:50% 100%;transform-box:fill-box;animation:lh-flame 23s cubic-bezier(.4,0,.2,1) infinite}
.lh-core{fill:url(#lh-cg);opacity:0;transform-origin:50% 100%;transform-box:fill-box;animation:lh-core 23s cubic-bezier(.4,0,.2,1) infinite}
.lh-cruc{fill:none;stroke:#a8a8a0;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:34;stroke-dashoffset:34;animation:lh-cruc 23s cubic-bezier(.4,0,.2,1) infinite}
.lh-word{fill:#bd9156;font-family:"Jost","Avenir Next",Futura,"Century Gothic","Helvetica Neue",system-ui,sans-serif;font-weight:500;letter-spacing:.22em;font-size:22px;opacity:0;animation:lh-word 23s cubic-bezier(.4,0,.2,1) infinite}
.lh-sub{fill:#f29a3d;font-family:"Jost","Avenir Next",Futura,"Century Gothic","Helvetica Neue",system-ui,sans-serif;font-weight:500;letter-spacing:.22em;font-size:14px;opacity:0;animation:lh-sub 23s cubic-bezier(.4,0,.2,1) infinite}
.lh-fbord{fill:none;stroke:#e86c2f;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;opacity:0;animation:lh-fbord 23s cubic-bezier(.4,0,.2,1) infinite}
.lh-code{opacity:0;animation:lh-code 23s cubic-bezier(.4,0,.2,1) infinite}
@keyframes lh-core{
0%{opacity:0;transform:scale(.5)} 1.3%{opacity:.75;transform:scale(1.05)}
3.5%{opacity:.55;transform:scale(.95)} 5.2%{opacity:.35;transform:scale(.92)}
22%{opacity:.55;transform:scale(1)} 37%{opacity:.24;transform:scale(.88)}
53%{opacity:.55;transform:scale(1)} 68%{opacity:.24;transform:scale(.88)}
83%{opacity:.55;transform:scale(1)} 99%{opacity:.35;transform:scale(.92)} 100%{opacity:0}
}
@keyframes lh-flame{
0%,6.5%{opacity:0;transform:scale(.4)} 8.7%{opacity:.9;transform:scale(1)}
10.4%{opacity:1;transform:scale(1.05)} 12%{opacity:1;transform:scale(1)}
99%{opacity:1;transform:scale(1)} 100%{opacity:0;transform:scale(.4)}
}
@keyframes lh-fbord{
0%,11.7%{opacity:0} 14.3%{opacity:1} 99%{opacity:1} 100%{opacity:0}
}
@keyframes lh-code{
0%,15.2%{opacity:0} 17.4%{opacity:1} 99%{opacity:1} 100%{opacity:0}
}
@keyframes lh-cruc{
0%,18.3%{stroke-dashoffset:34;opacity:0} 18.8%{opacity:.8}
22.6%{stroke-dashoffset:0;opacity:.8} 99%{stroke-dashoffset:0;opacity:.8}
100%{stroke-dashoffset:34;opacity:0}
}
@keyframes lh-sub{
0%,19.6%{opacity:0} 23.9%{opacity:.85} 99%{opacity:.85} 100%{opacity:0}
}
@keyframes lh-word{
0%,20.4%{opacity:0} 25.2%{opacity:1} 99%{opacity:1} 100%{opacity:0}
}
@media (prefers-reduced-motion:reduce){
.lh-flame,.lh-fbord,.lh-core,.lh-cruc,.lh-word,.lh-sub,.lh-code{animation:none}
.lh-flame{opacity:1} .lh-fbord{opacity:1} .lh-code{opacity:1}
.lh-core{opacity:.35;transform:scale(.95);transform-origin:50% 100%;transform-box:fill-box}
.lh-cruc{opacity:.8;stroke-dashoffset:0} .lh-word{opacity:1} .lh-sub{opacity:.85}
}
]]></style>
</defs>
<path class="lh-cruc" d="M 33 65 L 67 65"/>
<path class="lh-flame" d="M 50 62 C 33 57 33 37 50 23 C 67 37 67 57 50 62 Z"/>
<path class="lh-fbord" d="M 50 62 C 33 57 33 37 50 23 C 67 37 67 57 50 62 Z"/>
<path class="lh-core" d="M 50 57 C 41 52 41 40 50 32 C 59 40 59 52 50 57 Z"/>
<g class="lh-code" fill="none" stroke="#c25a1a" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<polyline points="46,42 43,46 46,50"/>
<line x1="51" y1="43" x2="48" y2="49"/>
<polyline points="54,42 57,46 54,50"/>
</g>
<text class="lh-word" x="80" y="46" text-anchor="start">lian</text>
<text class="lh-sub" x="83" y="64" text-anchor="start">build</text>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

29
assets/lian-v-static.svg Normal file
View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="85 19 56 100" preserveAspectRatio="xMidYMid meet" role="img" aria-label="Lian Build">
<title>Lian Build</title>
<defs>
<linearGradient id="lvs-fg" x1="0" y1="1" x2="0" y2="0">
<stop offset="0" stop-color="#e86c2f"/>
<stop offset="1" stop-color="#f5a623"/>
</linearGradient>
<linearGradient id="lvs-cg" x1="0" y1="1" x2="0" y2="0">
<stop offset="0" stop-color="#f5a623"/>
<stop offset="1" stop-color="#fcd99e"/>
</linearGradient>
<style><![CDATA[
@import url('https://fonts.googleapis.com/css2?family=Jost:wght@400;500&display=swap');
.lvs-word,.lvs-sub{font-family:"Jost","Avenir Next",Futura,"Century Gothic","Helvetica Neue",system-ui,sans-serif;font-weight:500;letter-spacing:.22em}
]]></style>
</defs>
<path fill="none" stroke="#a8a8a0" stroke-width="1.5" stroke-linecap="round" d="M 92 66 L 128 66" opacity=".8"/>
<path fill="url(#lvs-fg)" d="M 110 64 C 92 57 92 36 110 22 C 128 36 128 57 110 64 Z"/>
<path fill="none" stroke="#e86c2f" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" d="M 110 64 C 92 57 92 36 110 22 C 128 36 128 57 110 64 Z"/>
<path fill="url(#lvs-cg)" d="M 110 58 C 100 52 100 40 110 32 C 120 40 120 52 110 58 Z" opacity=".35" transform-origin="110 58" transform="scale(.92)"/>
<g fill="none" stroke="#c25a1a" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<polyline points="106,42 103,46 106,50"/>
<line x1="112" y1="43" x2="109" y2="49"/>
<polyline points="115,42 118,46 115,50"/>
</g>
<text class="lvs-word" fill="#bd9156" font-size="22" x="113" y="93" text-anchor="middle">lian</text>
<text class="lvs-sub" fill="#f29a3d" font-size="14" x="112" y="110" text-anchor="middle" opacity=".85">build</text>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

70
assets/lian-v.svg Normal file
View file

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="85 19 56 100" preserveAspectRatio="xMidYMid meet" role="img" aria-label="Lian Build">
<title>Lian Build</title>
<defs>
<linearGradient id="lv-fg" x1="0" y1="1" x2="0" y2="0">
<stop offset="0" stop-color="#e86c2f"/>
<stop offset="1" stop-color="#f5a623"/>
</linearGradient>
<linearGradient id="lv-cg" x1="0" y1="1" x2="0" y2="0">
<stop offset="0" stop-color="#f5a623"/>
<stop offset="1" stop-color="#fcd99e"/>
</linearGradient>
<style><![CDATA[
@import url('https://fonts.googleapis.com/css2?family=Jost:wght@400;500&display=swap');
.lv-flame{fill:url(#lv-fg);opacity:0;transform-origin:50% 100%;transform-box:fill-box;animation:lv-flame 23s cubic-bezier(.4,0,.2,1) infinite}
.lv-core{fill:url(#lv-cg);opacity:0;transform-origin:50% 100%;transform-box:fill-box;animation:lv-core 23s cubic-bezier(.4,0,.2,1) infinite}
.lv-cruc{fill:none;stroke:#a8a8a0;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:36;stroke-dashoffset:36;animation:lv-cruc 23s cubic-bezier(.4,0,.2,1) infinite}
.lv-word{fill:#bd9156;font-family:"Jost","Avenir Next",Futura,"Century Gothic","Helvetica Neue",system-ui,sans-serif;font-weight:500;letter-spacing:.22em;font-size:22px;opacity:0;animation:lv-word 23s cubic-bezier(.4,0,.2,1) infinite}
.lv-sub{fill:#f29a3d;font-family:"Jost","Avenir Next",Futura,"Century Gothic","Helvetica Neue",system-ui,sans-serif;font-weight:500;letter-spacing:.22em;font-size:14px;opacity:0;animation:lv-sub 23s cubic-bezier(.4,0,.2,1) infinite}
.lv-fbord{fill:none;stroke:#e86c2f;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;opacity:0;animation:lv-fbord 23s cubic-bezier(.4,0,.2,1) infinite}
.lv-code{opacity:0;animation:lv-code 23s cubic-bezier(.4,0,.2,1) infinite}
@keyframes lv-core{
0%{opacity:0;transform:scale(.5)} 1.3%{opacity:.75;transform:scale(1.05)}
3.5%{opacity:.55;transform:scale(.95)} 5.2%{opacity:.35;transform:scale(.92)}
22%{opacity:.55;transform:scale(1)} 37%{opacity:.24;transform:scale(.88)}
53%{opacity:.55;transform:scale(1)} 68%{opacity:.24;transform:scale(.88)}
83%{opacity:.55;transform:scale(1)} 99%{opacity:.35;transform:scale(.92)} 100%{opacity:0}
}
@keyframes lv-flame{
0%,6.5%{opacity:0;transform:scale(.4)} 8.7%{opacity:.9;transform:scale(1)}
10.4%{opacity:1;transform:scale(1.05)} 12%{opacity:1;transform:scale(1)}
99%{opacity:1;transform:scale(1)} 100%{opacity:0;transform:scale(.4)}
}
@keyframes lv-fbord{
0%,11.7%{opacity:0} 14.3%{opacity:1} 99%{opacity:1} 100%{opacity:0}
}
@keyframes lv-code{
0%,15.2%{opacity:0} 17.4%{opacity:1} 99%{opacity:1} 100%{opacity:0}
}
@keyframes lv-cruc{
0%,18.3%{stroke-dashoffset:36;opacity:0} 18.8%{opacity:.8}
22.6%{stroke-dashoffset:0;opacity:.8} 99%{stroke-dashoffset:0;opacity:.8}
100%{stroke-dashoffset:36;opacity:0}
}
@keyframes lv-sub{
0%,19.6%{opacity:0} 23.9%{opacity:.85} 99%{opacity:.85} 100%{opacity:0}
}
@keyframes lv-word{
0%,20.4%{opacity:0} 25.2%{opacity:1} 99%{opacity:1} 100%{opacity:0}
}
@media (prefers-reduced-motion:reduce){
.lv-flame,.lv-fbord,.lv-core,.lv-cruc,.lv-word,.lv-sub,.lv-code{animation:none}
.lv-flame{opacity:1} .lv-fbord{opacity:1} .lv-code{opacity:1}
.lv-core{opacity:.35;transform:scale(.95);transform-origin:50% 100%;transform-box:fill-box}
.lv-cruc{opacity:.8;stroke-dashoffset:0} .lv-word{opacity:1} .lv-sub{opacity:.85}
}
]]></style>
</defs>
<path class="lv-cruc" d="M 92 66 L 128 66"/>
<path class="lv-flame" d="M 110 64 C 92 57 92 36 110 22 C 128 36 128 57 110 64 Z"/>
<path class="lv-fbord" d="M 110 64 C 92 57 92 36 110 22 C 128 36 128 57 110 64 Z"/>
<path class="lv-core" d="M 110 58 C 100 52 100 40 110 32 C 120 40 120 52 110 58 Z"/>
<g class="lv-code" fill="none" stroke="#c25a1a" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<polyline points="106,42 103,46 106,50"/>
<line x1="112" y1="43" x2="109" y2="49"/>
<polyline points="115,42 118,46 115,50"/>
</g>
<text class="lv-word" x="113" y="93" text-anchor="middle">lian</text>
<text class="lv-sub" x="112" y="110" text-anchor="middle">build</text>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

13
assets/web/index.html Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="28 19 110 50" preserveAspectRatio="xMidYMid meet" role="img" aria-label="Lian Build">
<title>Lian Build</title>
<defs>
<linearGradient id="lhs-fg" x1="0" y1="1" x2="0" y2="0">
<stop offset="0" stop-color="#e86c2f"/>
<stop offset="1" stop-color="#f5a623"/>
</linearGradient>
<linearGradient id="lhs-cg" x1="0" y1="1" x2="0" y2="0">
<stop offset="0" stop-color="#f5a623"/>
<stop offset="1" stop-color="#fcd99e"/>
</linearGradient>
<style><![CDATA[
@import url('https://fonts.googleapis.com/css2?family=Jost:wght@400;500&display=swap');
.lhs-word,.lhs-sub{font-family:"Jost","Avenir Next",Futura,"Century Gothic","Helvetica Neue",system-ui,sans-serif;font-weight:500;letter-spacing:.22em}
]]></style>
</defs>
<path fill="none" stroke="#a8a8a0" stroke-width="1.5" stroke-linecap="round" d="M 33 65 L 67 65" opacity=".8"/>
<path fill="url(#lhs-fg)" d="M 50 62 C 33 57 33 37 50 23 C 67 37 67 57 50 62 Z"/>
<path fill="none" stroke="#e86c2f" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" d="M 50 62 C 33 57 33 37 50 23 C 67 37 67 57 50 62 Z"/>
<path fill="url(#lhs-cg)" d="M 50 57 C 41 52 41 40 50 32 C 59 40 59 52 50 57 Z" opacity=".35" transform="translate(50,57) scale(.92) translate(-50,-57)"/>
<g fill="none" stroke="#c25a1a" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<polyline points="46,42 43,46 46,50"/>
<line x1="51" y1="43" x2="48" y2="49"/>
<polyline points="54,42 57,46 54,50"/>
</g>
<text class="lhs-word" fill="#bd9156" font-size="22" x="80" y="46" text-anchor="start">lian</text>
<text class="lhs-sub" fill="#f29a3d" font-size="14" x="83" y="64" text-anchor="start" opacity=".85">build</text>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

70
assets/web/lian-h.svg Normal file
View file

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="28 19 110 50" preserveAspectRatio="xMidYMid meet" role="img" aria-label="Lian Build">
<title>Lian Build</title>
<defs>
<linearGradient id="lh-fg" x1="0" y1="1" x2="0" y2="0">
<stop offset="0" stop-color="#e86c2f"/>
<stop offset="1" stop-color="#f5a623"/>
</linearGradient>
<linearGradient id="lh-cg" x1="0" y1="1" x2="0" y2="0">
<stop offset="0" stop-color="#f5a623"/>
<stop offset="1" stop-color="#fcd99e"/>
</linearGradient>
<style><![CDATA[
@import url('https://fonts.googleapis.com/css2?family=Jost:wght@400;500&display=swap');
.lh-flame{fill:url(#lh-fg);opacity:0;transform-origin:50% 100%;transform-box:fill-box;animation:lh-flame 23s cubic-bezier(.4,0,.2,1) infinite}
.lh-core{fill:url(#lh-cg);opacity:0;transform-origin:50% 100%;transform-box:fill-box;animation:lh-core 23s cubic-bezier(.4,0,.2,1) infinite}
.lh-cruc{fill:none;stroke:#a8a8a0;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:34;stroke-dashoffset:34;animation:lh-cruc 23s cubic-bezier(.4,0,.2,1) infinite}
.lh-word{fill:#bd9156;font-family:"Jost","Avenir Next",Futura,"Century Gothic","Helvetica Neue",system-ui,sans-serif;font-weight:500;letter-spacing:.22em;font-size:22px;opacity:0;animation:lh-word 23s cubic-bezier(.4,0,.2,1) infinite}
.lh-sub{fill:#f29a3d;font-family:"Jost","Avenir Next",Futura,"Century Gothic","Helvetica Neue",system-ui,sans-serif;font-weight:500;letter-spacing:.22em;font-size:14px;opacity:0;animation:lh-sub 23s cubic-bezier(.4,0,.2,1) infinite}
.lh-fbord{fill:none;stroke:#e86c2f;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;opacity:0;animation:lh-fbord 23s cubic-bezier(.4,0,.2,1) infinite}
.lh-code{opacity:0;animation:lh-code 23s cubic-bezier(.4,0,.2,1) infinite}
@keyframes lh-core{
0%{opacity:0;transform:scale(.5)} 1.3%{opacity:.75;transform:scale(1.05)}
3.5%{opacity:.55;transform:scale(.95)} 5.2%{opacity:.35;transform:scale(.92)}
22%{opacity:.55;transform:scale(1)} 37%{opacity:.24;transform:scale(.88)}
53%{opacity:.55;transform:scale(1)} 68%{opacity:.24;transform:scale(.88)}
83%{opacity:.55;transform:scale(1)} 99%{opacity:.35;transform:scale(.92)} 100%{opacity:0}
}
@keyframes lh-flame{
0%,6.5%{opacity:0;transform:scale(.4)} 8.7%{opacity:.9;transform:scale(1)}
10.4%{opacity:1;transform:scale(1.05)} 12%{opacity:1;transform:scale(1)}
99%{opacity:1;transform:scale(1)} 100%{opacity:0;transform:scale(.4)}
}
@keyframes lh-fbord{
0%,11.7%{opacity:0} 14.3%{opacity:1} 99%{opacity:1} 100%{opacity:0}
}
@keyframes lh-code{
0%,15.2%{opacity:0} 17.4%{opacity:1} 99%{opacity:1} 100%{opacity:0}
}
@keyframes lh-cruc{
0%,18.3%{stroke-dashoffset:34;opacity:0} 18.8%{opacity:.8}
22.6%{stroke-dashoffset:0;opacity:.8} 99%{stroke-dashoffset:0;opacity:.8}
100%{stroke-dashoffset:34;opacity:0}
}
@keyframes lh-sub{
0%,19.6%{opacity:0} 23.9%{opacity:.85} 99%{opacity:.85} 100%{opacity:0}
}
@keyframes lh-word{
0%,20.4%{opacity:0} 25.2%{opacity:1} 99%{opacity:1} 100%{opacity:0}
}
@media (prefers-reduced-motion:reduce){
.lh-flame,.lh-fbord,.lh-core,.lh-cruc,.lh-word,.lh-sub,.lh-code{animation:none}
.lh-flame{opacity:1} .lh-fbord{opacity:1} .lh-code{opacity:1}
.lh-core{opacity:.35;transform:scale(.95);transform-origin:50% 100%;transform-box:fill-box}
.lh-cruc{opacity:.8;stroke-dashoffset:0} .lh-word{opacity:1} .lh-sub{opacity:.85}
}
]]></style>
</defs>
<path class="lh-cruc" d="M 33 65 L 67 65"/>
<path class="lh-flame" d="M 50 62 C 33 57 33 37 50 23 C 67 37 67 57 50 62 Z"/>
<path class="lh-fbord" d="M 50 62 C 33 57 33 37 50 23 C 67 37 67 57 50 62 Z"/>
<path class="lh-core" d="M 50 57 C 41 52 41 40 50 32 C 59 40 59 52 50 57 Z"/>
<g class="lh-code" fill="none" stroke="#c25a1a" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<polyline points="46,42 43,46 46,50"/>
<line x1="51" y1="43" x2="48" y2="49"/>
<polyline points="54,42 57,46 54,50"/>
</g>
<text class="lh-word" x="80" y="46" text-anchor="start">lian</text>
<text class="lh-sub" x="83" y="64" text-anchor="start">build</text>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

59
assets/web/lian-icon.svg Normal file
View file

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="30 20 40 48" preserveAspectRatio="xMidYMid meet" role="img" aria-label="Lian Build icon">
<title>Lian Build icon</title>
<defs>
<linearGradient id="li-fg" x1="0" y1="1" x2="0" y2="0">
<stop offset="0" stop-color="#e86c2f"/>
<stop offset="1" stop-color="#f5a623"/>
</linearGradient>
<linearGradient id="li-cg" x1="0" y1="1" x2="0" y2="0">
<stop offset="0" stop-color="#f5a623"/>
<stop offset="1" stop-color="#fcd99e"/>
</linearGradient>
<style><![CDATA[
.li-flame{fill:url(#li-fg);opacity:0;transform-origin:50% 100%;transform-box:fill-box;animation:li-flame 23s cubic-bezier(.4,0,.2,1) infinite}
.li-core{fill:url(#li-cg);opacity:0;transform-origin:50% 100%;transform-box:fill-box;animation:li-core 23s cubic-bezier(.4,0,.2,1) infinite}
.li-cruc{fill:none;stroke:#a8a8a0;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:34;stroke-dashoffset:34;animation:li-cruc 23s cubic-bezier(.4,0,.2,1) infinite}
.li-fbord{fill:none;stroke:#e86c2f;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;opacity:0;animation:li-fbord 23s cubic-bezier(.4,0,.2,1) infinite}
.li-code{opacity:0;animation:li-code 23s cubic-bezier(.4,0,.2,1) infinite}
@keyframes li-core{
0%{opacity:0;transform:scale(.5)} 1.3%{opacity:.75;transform:scale(1.05)}
3.5%{opacity:.55;transform:scale(.95)} 5.2%{opacity:.35;transform:scale(.92)}
22%{opacity:.55;transform:scale(1)} 37%{opacity:.24;transform:scale(.88)}
53%{opacity:.55;transform:scale(1)} 68%{opacity:.24;transform:scale(.88)}
83%{opacity:.55;transform:scale(1)} 99%{opacity:.35;transform:scale(.92)} 100%{opacity:0}
}
@keyframes li-flame{
0%,6.5%{opacity:0;transform:scale(.4)} 8.7%{opacity:.9;transform:scale(1)}
10.4%{opacity:1;transform:scale(1.05)} 12%{opacity:1;transform:scale(1)}
99%{opacity:1;transform:scale(1)} 100%{opacity:0;transform:scale(.4)}
}
@keyframes li-fbord{
0%,11.7%{opacity:0} 14.3%{opacity:1} 99%{opacity:1} 100%{opacity:0}
}
@keyframes li-code{
0%,15.2%{opacity:0} 17.4%{opacity:1} 99%{opacity:1} 100%{opacity:0}
}
@keyframes li-cruc{
0%,18.3%{stroke-dashoffset:34;opacity:0} 18.8%{opacity:.8}
22.6%{stroke-dashoffset:0;opacity:.8} 99%{stroke-dashoffset:0;opacity:.8}
100%{stroke-dashoffset:34;opacity:0}
}
@media (prefers-reduced-motion:reduce){
.li-flame,.li-fbord,.li-core,.li-cruc,.li-code{animation:none}
.li-flame{opacity:1} .li-fbord{opacity:1} .li-code{opacity:1}
.li-core{opacity:.35;transform:scale(.95);transform-origin:50% 100%;transform-box:fill-box}
.li-cruc{opacity:.8;stroke-dashoffset:0}
}
]]></style>
</defs>
<path class="li-cruc" d="M 33 65 L 67 65"/>
<path class="li-flame" d="M 50 62 C 33 57 33 37 50 23 C 67 37 67 57 50 62 Z"/>
<path class="li-fbord" d="M 50 62 C 33 57 33 37 50 23 C 67 37 67 57 50 62 Z"/>
<path class="li-core" d="M 50 57 C 41 52 41 40 50 32 C 59 40 59 52 50 57 Z"/>
<g class="li-code" fill="none" stroke="#c25a1a" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<polyline points="46,42 43,46 46,50"/>
<line x1="51" y1="43" x2="48" y2="49"/>
<polyline points="54,42 57,46 54,50"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

70
assets/web/lian-v.svg Normal file
View file

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="85 19 56 100" preserveAspectRatio="xMidYMid meet" role="img" aria-label="Lian Build">
<title>Lian Build</title>
<defs>
<linearGradient id="lv-fg" x1="0" y1="1" x2="0" y2="0">
<stop offset="0" stop-color="#e86c2f"/>
<stop offset="1" stop-color="#f5a623"/>
</linearGradient>
<linearGradient id="lv-cg" x1="0" y1="1" x2="0" y2="0">
<stop offset="0" stop-color="#f5a623"/>
<stop offset="1" stop-color="#fcd99e"/>
</linearGradient>
<style><![CDATA[
@import url('https://fonts.googleapis.com/css2?family=Jost:wght@400;500&display=swap');
.lv-flame{fill:url(#lv-fg);opacity:0;transform-origin:50% 100%;transform-box:fill-box;animation:lv-flame 23s cubic-bezier(.4,0,.2,1) infinite}
.lv-core{fill:url(#lv-cg);opacity:0;transform-origin:50% 100%;transform-box:fill-box;animation:lv-core 23s cubic-bezier(.4,0,.2,1) infinite}
.lv-cruc{fill:none;stroke:#a8a8a0;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:36;stroke-dashoffset:36;animation:lv-cruc 23s cubic-bezier(.4,0,.2,1) infinite}
.lv-word{fill:#bd9156;font-family:"Jost","Avenir Next",Futura,"Century Gothic","Helvetica Neue",system-ui,sans-serif;font-weight:500;letter-spacing:.22em;font-size:22px;opacity:0;animation:lv-word 23s cubic-bezier(.4,0,.2,1) infinite}
.lv-sub{fill:#f29a3d;font-family:"Jost","Avenir Next",Futura,"Century Gothic","Helvetica Neue",system-ui,sans-serif;font-weight:500;letter-spacing:.22em;font-size:14px;opacity:0;animation:lv-sub 23s cubic-bezier(.4,0,.2,1) infinite}
.lv-fbord{fill:none;stroke:#e86c2f;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;opacity:0;animation:lv-fbord 23s cubic-bezier(.4,0,.2,1) infinite}
.lv-code{opacity:0;animation:lv-code 23s cubic-bezier(.4,0,.2,1) infinite}
@keyframes lv-core{
0%{opacity:0;transform:scale(.5)} 1.3%{opacity:.75;transform:scale(1.05)}
3.5%{opacity:.55;transform:scale(.95)} 5.2%{opacity:.35;transform:scale(.92)}
22%{opacity:.55;transform:scale(1)} 37%{opacity:.24;transform:scale(.88)}
53%{opacity:.55;transform:scale(1)} 68%{opacity:.24;transform:scale(.88)}
83%{opacity:.55;transform:scale(1)} 99%{opacity:.35;transform:scale(.92)} 100%{opacity:0}
}
@keyframes lv-flame{
0%,6.5%{opacity:0;transform:scale(.4)} 8.7%{opacity:.9;transform:scale(1)}
10.4%{opacity:1;transform:scale(1.05)} 12%{opacity:1;transform:scale(1)}
99%{opacity:1;transform:scale(1)} 100%{opacity:0;transform:scale(.4)}
}
@keyframes lv-fbord{
0%,11.7%{opacity:0} 14.3%{opacity:1} 99%{opacity:1} 100%{opacity:0}
}
@keyframes lv-code{
0%,15.2%{opacity:0} 17.4%{opacity:1} 99%{opacity:1} 100%{opacity:0}
}
@keyframes lv-cruc{
0%,18.3%{stroke-dashoffset:36;opacity:0} 18.8%{opacity:.8}
22.6%{stroke-dashoffset:0;opacity:.8} 99%{stroke-dashoffset:0;opacity:.8}
100%{stroke-dashoffset:36;opacity:0}
}
@keyframes lv-sub{
0%,19.6%{opacity:0} 23.9%{opacity:.85} 99%{opacity:.85} 100%{opacity:0}
}
@keyframes lv-word{
0%,20.4%{opacity:0} 25.2%{opacity:1} 99%{opacity:1} 100%{opacity:0}
}
@media (prefers-reduced-motion:reduce){
.lv-flame,.lv-fbord,.lv-core,.lv-cruc,.lv-word,.lv-sub,.lv-code{animation:none}
.lv-flame{opacity:1} .lv-fbord{opacity:1} .lv-code{opacity:1}
.lv-core{opacity:.35;transform:scale(.95);transform-origin:50% 100%;transform-box:fill-box}
.lv-cruc{opacity:.8;stroke-dashoffset:0} .lv-word{opacity:1} .lv-sub{opacity:.85}
}
]]></style>
</defs>
<path class="lv-cruc" d="M 92 66 L 128 66"/>
<path class="lv-flame" d="M 110 64 C 92 57 92 36 110 22 C 128 36 128 57 110 64 Z"/>
<path class="lv-fbord" d="M 110 64 C 92 57 92 36 110 22 C 128 36 128 57 110 64 Z"/>
<path class="lv-core" d="M 110 58 C 100 52 100 40 110 32 C 120 40 120 52 110 58 Z"/>
<g class="lv-code" fill="none" stroke="#c25a1a" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<polyline points="106,42 103,46 106,50"/>
<line x1="112" y1="43" x2="109" y2="49"/>
<polyline points="115,42 118,46 115,50"/>
</g>
<text class="lv-word" x="113" y="93" text-anchor="middle">lian</text>
<text class="lv-sub" x="112" y="110" text-anchor="middle">build</text>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

110
assets/web/minify.sh Executable file
View file

@ -0,0 +1,110 @@
#!/usr/bin/env bash
# Minify HTML files from src/ to production versions in this directory.
# Adapted from /Users/Akasha/Development/ontoref/assets/web/minify.sh.
# Usage: ./minify.sh
set -euo pipefail
BASE_DIR="$(cd "$(dirname "$0")" && pwd)"
FILES=("index.html")
minify_file() {
local filename="$1"
local SRC_FILE="${BASE_DIR}/src/${filename}"
local OUT_FILE="${BASE_DIR}/${filename}"
local TEMP_FILE="${OUT_FILE}.tmp"
if [ ! -f "$SRC_FILE" ]; then
echo " Source file not found: $SRC_FILE (skipping)"
return 0
fi
echo ""
echo "Minifying ${filename}..."
echo " Input: $SRC_FILE"
echo " Output: $OUT_FILE"
perl -e "
use strict;
use warnings;
open(my \$fh, '<', '$SRC_FILE') or die \$!;
my \$content = do { local \$/; <\$fh> };
close(\$fh);
# Preserve <pre>, <code>, and <textarea> blocks verbatim.
my @preserved;
\$content =~ s{(<(pre|code|textarea)\b[^>]*>.*?</\2>)}{
push @preserved, \$1;
'__PRESERVED_' . (\$#preserved) . '__';
}gies;
# Remove HTML comments
\$content =~ s/<!--.*?-->//gs;
# Compress CSS (remove block comments and collapse whitespace)
\$content =~ s/(<style[^>]*>)(.*?)(<\/style>)/
my \$before = \$1;
my \$style = \$2;
my \$after = \$3;
\$style =~ s{\/\*.*?\*\/}{}gs;
\$style =~ s{\s+}{ }gs;
\$style =~ s{\s*([{}:;,>+~])\s*}{\$1}gs;
\$before . \$style . \$after;
/gies;
# Compress JavaScript (strip line comments and collapse whitespace)
\$content =~ s/(<script[^>]*>)(.*?)(<\/script>)/
my \$before = \$1;
my \$script = \$2;
my \$after = \$3;
\$script =~ s{\/\/[^\n\r]*}{}g;
\$script =~ s{\s+}{ }gs;
\$script =~ s{\s*([{}();,])\s*}{\$1}gs;
\$before . \$script . \$after;
/gies;
# Remove whitespace between tags
\$content =~ s/>\s+</></gs;
# Compress general whitespace
\$content =~ s/\s+/ /gs;
# Trim
\$content =~ s/^\s+|\s+\$//g;
# Restore preserved blocks
\$content =~ s/__PRESERVED_(\d+)__/\$preserved[\$1]/g;
open(my \$out, '>', '$TEMP_FILE') or die \$!;
print \$out \$content;
close(\$out);
" || {
echo "Minification failed"
rm -f "$TEMP_FILE"
exit 1
}
mv "$TEMP_FILE" "$OUT_FILE"
original=$(wc -c <"$SRC_FILE")
minified=$(wc -c <"$OUT_FILE")
saved=$((original - minified))
percent=$((saved * 100 / original))
echo ""
echo " Compression statistics:"
printf " Original: %6d bytes\n" "$original"
printf " Minified: %6d bytes\n" "$minified"
printf " Saved: %6d bytes (%d%%)\n" "$saved" "$percent"
echo " ${filename} ready for production"
}
echo "Starting HTML minification..."
for file in "${FILES[@]}"; do
minify_file "$file"
done
echo ""
echo "All files minified successfully."

1886
assets/web/src/index.html Normal file

File diff suppressed because it is too large Load diff