Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9617005a5c | |||
| 73a3381d2a | |||
| fbfb265570 | |||
| eda9b77f46 | |||
| 64ab54daf3 | |||
| 0e1594773b | |||
| 7f5beb401e | |||
| a606d62904 | |||
| bc49286f91 | |||
| 1e252ff6f2 | |||
| d12a7debdf | |||
| 30d9e4e4df | |||
| 606b38e022 | |||
| 4d9fa45ab6 | |||
| 8a4e0ba0e4 | |||
| 93df69a43e | |||
| 6a5819b905 | |||
| 389d4b8008 | |||
| ce1ed17ce1 | |||
| ac4bcb4217 | |||
| 01861340ad | |||
| 6091d4bf58 | |||
| bc9f4d9867 | |||
| 2756d95ea5 |
@@ -0,0 +1,78 @@
|
||||
name: Release Creation
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo "💡 The ${{ gitea.repository }} repository will be cloned to the runner."
|
||||
|
||||
- uses: https://github.com/RouxAntoine/checkout@v3.5.4
|
||||
|
||||
# Get part of the tag after the `v`
|
||||
- name: Extract tag version number
|
||||
id: get_version
|
||||
uses: https://github.com/battila7/get-version-action@v2
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: https://github.com/actions/setup-node@v3
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
# Génération à la volée des compendiums (packs-src/ → packs/ LevelDB)
|
||||
- name: Compile compendium packs
|
||||
run: npm run pack:compile
|
||||
|
||||
# Compilation CSS (LESS → css/) et bundle JavaScript (src/ → dist/)
|
||||
- name: Build CSS and JavaScript
|
||||
run: npm run build
|
||||
|
||||
# Substitute the Manifest and Download URLs in the system.json
|
||||
- name: Substitute Manifest and Download Links For Versioned Ones
|
||||
id: sub_manifest_link_version
|
||||
uses: https://github.com/microsoft/variable-substitution@v1
|
||||
with:
|
||||
files: "system.json"
|
||||
env:
|
||||
version: ${{steps.get_version.outputs.version-without-v}}
|
||||
url: https://www.uberwald.me/gitea/${{gitea.repository}}
|
||||
manifest: https://www.uberwald.me/gitea/uberwald/fvtt-chroniques-de-l-etrange/releases/download/latest/system.json
|
||||
download: https://www.uberwald.me/gitea/uberwald/fvtt-chroniques-de-l-etrange/releases/download/${{github.event.release.tag_name}}/fvtt-chroniques-de-l-etrange.zip
|
||||
|
||||
# Create a zip file with all files required by the system
|
||||
- run: |
|
||||
apt update -y
|
||||
apt install -y zip
|
||||
|
||||
- run: zip -r ./fvtt-chroniques-de-l-etrange.zip system.json README.md CHANGELOG.md LICENSE.txt css/ fonts/ images/ lang/ packs/ src/ templates/
|
||||
|
||||
- name: Setup Go
|
||||
uses: https://github.com/actions/setup-go@v4
|
||||
with:
|
||||
go-version: ">=1.20.1"
|
||||
|
||||
- name: Upload release assets
|
||||
id: use-go-action
|
||||
uses: https://gitea.com/actions/release-action@main
|
||||
with:
|
||||
files: |-
|
||||
./fvtt-chroniques-de-l-etrange.zip
|
||||
system.json
|
||||
api_key: "${{secrets.ALLOW_PUSH_RELEASE}}"
|
||||
|
||||
#- name: Publish to Foundry server
|
||||
# uses: https://github.com/djlechuck/foundryvtt-publish-package-action@v1
|
||||
# with:
|
||||
# #token: ${{ secrets.FOUNDRYVTT_RELEASE_TOKEN }}
|
||||
# id: 'fvtt-chroniques-de-l-etrange'
|
||||
# version: ${{github.event.release.tag_name}}
|
||||
# manifest: 'https://www.uberwald.me/gitea/public/fvtt-chroniques-de-l-etrange/releases/download/latest/system.json'
|
||||
# notes: 'https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/fvtt-chroniques-de-l-etrange.zip'
|
||||
# compatibility-minimum: '13'
|
||||
# compatibility-verified: '13'
|
||||
@@ -5,8 +5,9 @@
|
||||
|
||||
# Node Modules
|
||||
node_modules/
|
||||
package-lock.json
|
||||
|
||||
chroniquesdeletrange.lock
|
||||
*.pdf
|
||||
*.github/
|
||||
regles.txt
|
||||
regles.txt
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
# Les Chroniques de l'étrange pour FoundryVTT
|
||||
# Chroniques de l'étrange — Système FoundryVTT
|
||||
|
||||
Système [Foundry VTT](https://foundryvtt.com) pour **Chroniques de l'Etrange**, le jeu de rôle d'[Antre Monde Éditions](https://antremonde.fr).
|
||||
|
||||
Copyright 2025-2026 Antre Monde Editions All rights reserved
|
||||
|
||||
Chroniques de l'ETrange is a game written by Romain d'Huissier and Cédric Lameire. The authors retain their moral rights to this work in both print and digital formats.
|
||||
|
||||
This system for FoundryVTT has been approved and authorized by Antre-Monde Edition
|
||||
|
||||
---
|
||||
|
||||
Implémentation du JDR Les Chroniques de l'Etrange de Antre-Monde éditions.
|
||||
|
||||
@@ -177,6 +177,11 @@
|
||||
flex: 1 1 110px;
|
||||
max-width: 200px;
|
||||
}
|
||||
.cde-stat-cell--wide {
|
||||
flex: 1 1 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
.cde-stat-label {
|
||||
font-size: 10px;
|
||||
font-family: "Averia", sans-serif;
|
||||
@@ -205,6 +210,23 @@
|
||||
.cde-stat-cell input:focus {
|
||||
border-bottom-color: #00d4d4;
|
||||
}
|
||||
.cde-stat-cell textarea {
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
border: 1px solid #1a2436;
|
||||
border-radius: 2px;
|
||||
color: #e2e8f4;
|
||||
font-size: 13px;
|
||||
font-family: inherit;
|
||||
padding: 4px 6px;
|
||||
outline: none;
|
||||
resize: vertical;
|
||||
transition: border-color 0.15s;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.cde-stat-cell textarea:focus {
|
||||
border-color: #ff3d5a;
|
||||
}
|
||||
.cde-stat-cell select {
|
||||
width: 100%;
|
||||
border: none;
|
||||
@@ -385,7 +407,6 @@ section.npc .cde-neon-tabs .item.active {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
min-height: 100%;
|
||||
}
|
||||
.cde-tab-body .tab.active {
|
||||
display: flex;
|
||||
@@ -1254,6 +1275,12 @@ section.npc .cde-neon-tabs .item.active {
|
||||
width: auto;
|
||||
height: auto;
|
||||
opacity: 0.6;
|
||||
cursor: zoom-in;
|
||||
transition: opacity 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
.cde-loksyu-standalone .cde-loksyu-visual-row .cde-lok-visual:hover {
|
||||
opacity: 0.9;
|
||||
box-shadow: 0 0 10px #263853;
|
||||
}
|
||||
.cde-loksyu-standalone .cde-lok-footer {
|
||||
display: flex;
|
||||
@@ -3080,6 +3107,12 @@ strong.ellipsis {
|
||||
max-height: 180px;
|
||||
opacity: 0.45;
|
||||
border-radius: 4px;
|
||||
cursor: zoom-in;
|
||||
transition: opacity 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
.cde-nghang-diagram img:hover {
|
||||
opacity: 0.85;
|
||||
box-shadow: 0 0 12px #263853;
|
||||
}
|
||||
.img-die-sm {
|
||||
width: 27px !important;
|
||||
@@ -3800,3 +3833,626 @@ ol.item-list li.item .item-controls a.item-control:hover {
|
||||
color: #ff3d5a;
|
||||
text-shadow: 0 0 5px rgba(255, 61, 90, 0.4);
|
||||
}
|
||||
/* ============================================================
|
||||
ROUE D'INITIATIVE — CDEWheelApp
|
||||
============================================================ */
|
||||
.cde-wheel-app {
|
||||
color: #e2e8f4;
|
||||
background: #080c14;
|
||||
font-family: "Averia", "Averia Regular", sans-serif;
|
||||
}
|
||||
.cde-wheel-app .window-content {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
/* Two-column layout: SVG wheel left, panel right */
|
||||
.cde-wheel-layout {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
min-height: 520px;
|
||||
}
|
||||
/* ---- Left: SVG wheel ---- */
|
||||
.cde-wheel-svg-container {
|
||||
flex: 0 0 480px;
|
||||
width: 480px;
|
||||
padding: 12px 12px 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #0d1520;
|
||||
border-right: 1px solid #1a2436;
|
||||
}
|
||||
.cde-wheel-svg-container svg {
|
||||
flex: 1 1 auto;
|
||||
width: 100%;
|
||||
max-width: 456px;
|
||||
max-height: 456px;
|
||||
overflow: visible;
|
||||
}
|
||||
.cde-wheel-svg-container .cde-wheel-legend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 4px 0 2px;
|
||||
font-size: 11px;
|
||||
color: rgba(255, 255, 255, 0.55);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.cde-wheel-svg-container .cde-wheel-legend-dot {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.cde-wheel-svg-container .cde-wheel-segment {
|
||||
stroke: #080c14;
|
||||
stroke-width: 1.5;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
.cde-wheel-svg-container .cde-wheel-cran-label {
|
||||
font-family: "Averia Regular", sans-serif;
|
||||
font-size: 12px;
|
||||
fill: rgba(255, 255, 255, 0.55);
|
||||
text-anchor: middle;
|
||||
dominant-baseline: central;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
.cde-wheel-svg-container .cde-wheel-fighter-circle {
|
||||
stroke-width: 2;
|
||||
cursor: pointer;
|
||||
transition: r 0.2s, stroke-width 0.2s;
|
||||
}
|
||||
.cde-wheel-svg-container .cde-wheel-fighter-circle:hover {
|
||||
stroke-width: 3;
|
||||
}
|
||||
.cde-wheel-svg-container .cde-wheel-fighter-circle.is-active {
|
||||
r: 18;
|
||||
stroke-width: 3;
|
||||
filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.7));
|
||||
}
|
||||
.cde-wheel-svg-container .cde-wheel-fighter-circle.is-turn {
|
||||
stroke-dasharray: 3 2;
|
||||
animation: cde-spin 4s linear infinite;
|
||||
}
|
||||
.cde-wheel-svg-container .cde-wheel-fighter-initial {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
fill: #fff;
|
||||
text-anchor: middle;
|
||||
dominant-baseline: central;
|
||||
pointer-events: none;
|
||||
}
|
||||
/* ---- Right: panel ---- */
|
||||
.cde-wheel-panel {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
.cde-wheel-section-title {
|
||||
padding: 7px 12px 6px;
|
||||
border-bottom: 1px solid #1a2436;
|
||||
background: #0d1520;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: #7d94b8;
|
||||
border-left: 3px solid #4a9eff;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.cde-wheel-section-title em {
|
||||
font-style: normal;
|
||||
color: #e2e8f4;
|
||||
text-transform: none;
|
||||
letter-spacing: 0;
|
||||
font-size: 11px;
|
||||
}
|
||||
/* Combatant list */
|
||||
.cde-wheel-combatants {
|
||||
flex: 0 0 auto;
|
||||
max-height: 130px;
|
||||
overflow-y: auto;
|
||||
border-bottom: 1px solid #1a2436;
|
||||
}
|
||||
.cde-wheel-combatant {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 12px 6px 9px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid rgba(26, 36, 54, 0.5);
|
||||
border-left: 3px solid transparent;
|
||||
transition: background 0.15s, border-color 0.15s;
|
||||
}
|
||||
.cde-wheel-combatant:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.cde-wheel-combatant:hover {
|
||||
background: #101622;
|
||||
}
|
||||
.cde-wheel-combatant.cde-wheel-combatant--selected {
|
||||
background: rgba(74, 158, 255, 0.1);
|
||||
border-left-color: #4a9eff;
|
||||
}
|
||||
.cde-wheel-combatant.cde-wheel-combatant--selected .cde-wheel-combatant-name {
|
||||
color: #e2e8f4;
|
||||
font-weight: 600;
|
||||
}
|
||||
.cde-wheel-combatant.cde-wheel-combatant--active .cde-wheel-active-marker {
|
||||
color: #f0c040;
|
||||
filter: drop-shadow(0 0 3px #f0c040);
|
||||
}
|
||||
.cde-wheel-combatant-img {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
border: 1.5px solid #263853;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.cde-wheel-combatant-name {
|
||||
flex: 1 1 auto;
|
||||
font-size: 13px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: #7d94b8;
|
||||
}
|
||||
.cde-wheel-combatant-cran {
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
font-variant-numeric: tabular-nums;
|
||||
flex-shrink: 0;
|
||||
min-width: 22px;
|
||||
text-align: center;
|
||||
padding: 2px 6px;
|
||||
border-radius: 12px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
.cde-wheel-active-marker {
|
||||
font-size: 10px;
|
||||
color: #7d94b8;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
/* Action area */
|
||||
.cde-wheel-actions {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
padding: 8px 10px 10px;
|
||||
gap: 6px;
|
||||
}
|
||||
.cde-wheel-actions.cde-wheel-actions--hint {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.cde-wheel-hint {
|
||||
color: #7d94b8;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.cde-wheel-hint i {
|
||||
font-size: 22px;
|
||||
opacity: 0.4;
|
||||
}
|
||||
.cde-wheel-action-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 5px;
|
||||
}
|
||||
.cde-wheel-action-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 4px;
|
||||
background: #101622;
|
||||
border: 1px solid #263853;
|
||||
border-radius: 5px;
|
||||
color: #7d94b8;
|
||||
font-size: 11px;
|
||||
padding: 5px 7px;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
transition: background 0.15s, border-color 0.15s, color 0.15s, box-shadow 0.15s;
|
||||
font-family: inherit;
|
||||
}
|
||||
.cde-wheel-action-btn:hover {
|
||||
color: #e2e8f4;
|
||||
background: #1a2436;
|
||||
border-color: #263853;
|
||||
}
|
||||
.cde-wheel-action-btn .cde-wheel-action-name {
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.cde-wheel-action-btn .cde-wheel-action-cost {
|
||||
flex-shrink: 0;
|
||||
font-weight: 700;
|
||||
font-size: 10px;
|
||||
padding: 1px 5px;
|
||||
border-radius: 10px;
|
||||
background: #263853;
|
||||
color: #7d94b8;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.cde-wheel-action-btn[data-cost="1"]:hover {
|
||||
box-shadow: 0 0 6px rgba(74, 158, 255, 0.22);
|
||||
}
|
||||
.cde-wheel-action-btn[data-cost="1"] .cde-wheel-action-cost {
|
||||
background: #1a3d6a;
|
||||
color: #6aadff;
|
||||
}
|
||||
.cde-wheel-action-btn[data-cost="2"]:hover {
|
||||
box-shadow: 0 0 6px rgba(212, 160, 80, 0.25);
|
||||
}
|
||||
.cde-wheel-action-btn[data-cost="2"] .cde-wheel-action-cost {
|
||||
background: #4a3200;
|
||||
color: #d4a050;
|
||||
}
|
||||
.cde-wheel-action-btn[data-cost="3"]:hover {
|
||||
box-shadow: 0 0 6px rgba(224, 96, 48, 0.28);
|
||||
}
|
||||
.cde-wheel-action-btn[data-cost="3"] .cde-wheel-action-cost {
|
||||
background: #4a1800;
|
||||
color: #e07840;
|
||||
}
|
||||
.cde-wheel-action-btn[data-cost="6"] {
|
||||
border-color: rgba(204, 32, 64, 0.4);
|
||||
}
|
||||
.cde-wheel-action-btn[data-cost="6"]:hover {
|
||||
box-shadow: 0 0 6px rgba(204, 32, 64, 0.35);
|
||||
}
|
||||
.cde-wheel-action-btn[data-cost="6"] .cde-wheel-action-cost {
|
||||
background: #4a0814;
|
||||
color: #e03050;
|
||||
}
|
||||
/* Special action buttons */
|
||||
.cde-wheel-special-actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.cde-wheel-btn-roll,
|
||||
.cde-wheel-btn-surprise {
|
||||
flex: 1 1 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
border-radius: 5px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
padding: 7px 8px;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
transition: background 0.15s, box-shadow 0.15s;
|
||||
border: 1px solid;
|
||||
}
|
||||
.cde-wheel-btn-roll {
|
||||
background: rgba(192, 138, 0, 0.14);
|
||||
border-color: rgba(192, 138, 0, 0.5);
|
||||
color: #e0b030;
|
||||
}
|
||||
.cde-wheel-btn-roll:hover {
|
||||
background: rgba(192, 138, 0, 0.26);
|
||||
box-shadow: 0 0 8px rgba(192, 138, 0, 0.4);
|
||||
}
|
||||
.cde-wheel-btn-surprise {
|
||||
background: rgba(255, 61, 90, 0.12);
|
||||
border-color: rgba(255, 61, 90, 0.45);
|
||||
color: #ff3d5a;
|
||||
}
|
||||
.cde-wheel-btn-surprise:hover {
|
||||
background: rgba(255, 61, 90, 0.24);
|
||||
box-shadow: 0 0 8px rgba(255, 61, 90, 0.35);
|
||||
}
|
||||
/* No-combat empty state */
|
||||
.cde-wheel-no-combat {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #7d94b8;
|
||||
gap: 8px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
.cde-wheel-no-combat i {
|
||||
font-size: 28px;
|
||||
opacity: 0.4;
|
||||
}
|
||||
/* Spin animation for active-turn token */
|
||||
@keyframes cde-spin {
|
||||
from {
|
||||
transform-origin: var(--fx) var(--fy);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform-origin: var(--fx) var(--fy);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
/* ===================================================================
|
||||
Migration App
|
||||
=================================================================== */
|
||||
.cde-migration-app .window-content {
|
||||
padding: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.cde-migration-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
}
|
||||
/* Drop zone */
|
||||
.cde-migration-drop-zone {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 28px 20px;
|
||||
border: 2px dashed #1a2436;
|
||||
border-radius: 8px;
|
||||
background: rgba(13, 21, 32, 0.6);
|
||||
text-align: center;
|
||||
transition: border-color 0.15s, background 0.15s;
|
||||
cursor: pointer;
|
||||
}
|
||||
.cde-migration-drop-zone.is-dragover {
|
||||
border-color: #4a9eff;
|
||||
background: rgba(74, 158, 255, 0.15);
|
||||
}
|
||||
.cde-migration-drop-icon {
|
||||
font-size: 36px;
|
||||
color: #4a9eff;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.cde-migration-drop-hint {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: #7d94b8;
|
||||
}
|
||||
.cde-migration-file-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 14px;
|
||||
border: 1px solid #4a9eff;
|
||||
border-radius: 4px;
|
||||
color: #4a9eff;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.cde-migration-file-btn:hover {
|
||||
background: rgba(74, 158, 255, 0.2);
|
||||
}
|
||||
/* Preview section */
|
||||
.cde-migration-preview {
|
||||
border: 1px solid #1a2436;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.cde-migration-preview-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 12px;
|
||||
background: rgba(13, 21, 32, 0.8);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: #7d94b8;
|
||||
}
|
||||
.cde-migration-clear-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 2px 8px;
|
||||
border: 1px solid #1a2436;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
color: #7d94b8;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
transition: color 0.15s, border-color 0.15s;
|
||||
}
|
||||
.cde-migration-clear-btn:hover {
|
||||
color: #e04444;
|
||||
border-color: #e04444;
|
||||
}
|
||||
/* Preview table */
|
||||
.cde-migration-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 12px;
|
||||
}
|
||||
.cde-migration-table th {
|
||||
padding: 5px 8px;
|
||||
background: rgba(13, 21, 32, 0.9);
|
||||
color: #7d94b8;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #1a2436;
|
||||
}
|
||||
.cde-migration-table td {
|
||||
padding: 5px 8px;
|
||||
border-bottom: 1px solid rgba(26, 36, 54, 0.4);
|
||||
vertical-align: middle;
|
||||
}
|
||||
.cde-migration-table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
.cde-migration-thumb {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 3px;
|
||||
object-fit: cover;
|
||||
}
|
||||
.cde-migration-name {
|
||||
font-weight: 600;
|
||||
color: #e2e8f4;
|
||||
}
|
||||
.cde-migration-items-count {
|
||||
text-align: center;
|
||||
color: #7d94b8;
|
||||
}
|
||||
.cde-migration-srcfile {
|
||||
font-size: 10px;
|
||||
color: #7d94b8;
|
||||
max-width: 130px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
/* Type badge */
|
||||
.cde-migration-type-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.cde-migration-type-badge.cde-migration-type-character {
|
||||
background: rgba(74, 158, 255, 0.2);
|
||||
color: #4a9eff;
|
||||
border: 1px solid rgba(74, 158, 255, 0.4);
|
||||
}
|
||||
.cde-migration-type-badge.cde-migration-type-npc {
|
||||
background: rgba(156, 77, 204, 0.2);
|
||||
color: #c97ae0;
|
||||
border: 1px solid rgba(156, 77, 204, 0.4);
|
||||
}
|
||||
/* Errors */
|
||||
.cde-migration-errors {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
.cde-migration-errors li {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 6px;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid rgba(224, 68, 68, 0.6);
|
||||
border-radius: 4px;
|
||||
background: rgba(224, 68, 68, 0.1);
|
||||
color: #e07070;
|
||||
font-size: 11px;
|
||||
}
|
||||
.cde-migration-errors li i {
|
||||
margin-top: 2px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
/* Bottom action bar */
|
||||
.cde-migration-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-top: 4px;
|
||||
}
|
||||
.cde-migration-import-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 9px 24px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
background: #4a9eff;
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: filter 0.15s;
|
||||
}
|
||||
.cde-migration-import-btn:hover {
|
||||
filter: brightness(1.15);
|
||||
}
|
||||
.cde-migration-hint {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: #7d94b8;
|
||||
text-align: center;
|
||||
}
|
||||
.cde-welcome-message {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 16px;
|
||||
background: #101622;
|
||||
border: 1px solid #263853;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
.cde-welcome-logo {
|
||||
width: 120px;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
filter: drop-shadow(0 0 8px rgba(74, 158, 255, 0.4));
|
||||
}
|
||||
.cde-welcome-title {
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #4a9eff;
|
||||
text-shadow: 0 0 8px rgba(74, 158, 255, 0.5);
|
||||
}
|
||||
.cde-welcome-links {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: #7d94b8;
|
||||
}
|
||||
.cde-welcome-links a {
|
||||
color: #00d4d4;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px solid rgba(0, 212, 212, 0.4);
|
||||
}
|
||||
.cde-welcome-links a:hover {
|
||||
color: #fff;
|
||||
border-bottom-color: #fff;
|
||||
}
|
||||
.cde-welcome-help-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-top: 4px;
|
||||
padding: 7px 18px;
|
||||
background: #4a9eff;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: filter 0.15s;
|
||||
}
|
||||
.cde-welcome-help-btn:hover {
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
|
||||
@@ -208,6 +208,12 @@
|
||||
min-width: 110px;
|
||||
flex: 1 1 110px;
|
||||
max-width: 200px;
|
||||
|
||||
&--wide {
|
||||
flex: 1 1 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.cde-stat-label {
|
||||
@@ -240,6 +246,23 @@
|
||||
&:focus { border-bottom-color: @cde-item; }
|
||||
}
|
||||
|
||||
.cde-stat-cell textarea {
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
border: 1px solid @cde-border;
|
||||
border-radius: 2px;
|
||||
color: @cde-text;
|
||||
font-size: 13px;
|
||||
font-family: inherit;
|
||||
padding: 4px 6px;
|
||||
outline: none;
|
||||
resize: vertical;
|
||||
transition: border-color 0.15s;
|
||||
line-height: 1.5;
|
||||
|
||||
&:focus { border-color: @cde-kungfu; }
|
||||
}
|
||||
|
||||
.cde-stat-cell select {
|
||||
width: 100%;
|
||||
border: none;
|
||||
@@ -369,7 +392,6 @@ section.npc .cde-neon-tabs .item.active { color: @cde-supernatural; borde
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: @cde-gap;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
@@ -1289,6 +1311,13 @@ section.npc .cde-neon-tabs .item.active { color: @cde-supernatural; borde
|
||||
width: auto;
|
||||
height: auto;
|
||||
opacity: 0.6;
|
||||
cursor: zoom-in;
|
||||
transition: opacity 0.2s ease, box-shadow 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.9;
|
||||
box-shadow: 0 0 10px @cde-border-hi;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3131,6 +3160,13 @@ strong.ellipsis {
|
||||
max-height: 180px;
|
||||
opacity: 0.45;
|
||||
border-radius: 4px;
|
||||
cursor: zoom-in;
|
||||
transition: opacity 0.2s ease, box-shadow 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.85;
|
||||
box-shadow: 0 0 12px @cde-border-hi;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3915,3 +3951,706 @@ ol.item-list {
|
||||
text-shadow: 0 0 5px fade(@cde-kungfu, 40%);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ============================================================
|
||||
ROUE D'INITIATIVE — CDEWheelApp
|
||||
============================================================ */
|
||||
|
||||
// Wu Xing segment colours (match JS constants)
|
||||
@wu-metal: #b8c4cc;
|
||||
@wu-water: #3a7bd5;
|
||||
@wu-earth: #c8a84b;
|
||||
@wu-fire: #d94f3d;
|
||||
@wu-wood: #4a9b5a;
|
||||
@wu-reference: #2c1f6b;
|
||||
|
||||
.cde-wheel-app {
|
||||
color: @cde-text;
|
||||
background: @cde-bg;
|
||||
font-family: "Averia", "Averia Regular", sans-serif;
|
||||
|
||||
.window-content {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
/* Two-column layout: SVG wheel left, panel right */
|
||||
.cde-wheel-layout {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
min-height: 520px;
|
||||
}
|
||||
|
||||
/* ---- Left: SVG wheel ---- */
|
||||
.cde-wheel-svg-container {
|
||||
flex: 0 0 480px;
|
||||
width: 480px;
|
||||
padding: 12px 12px 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: @cde-surface2;
|
||||
border-right: 1px solid @cde-border;
|
||||
|
||||
svg {
|
||||
flex: 1 1 auto;
|
||||
width: 100%;
|
||||
max-width: 456px;
|
||||
max-height: 456px;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.cde-wheel-legend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 4px 0 2px;
|
||||
font-size: 11px;
|
||||
color: rgba(255,255,255,0.55);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.cde-wheel-legend-dot {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.cde-wheel-segment {
|
||||
stroke: @cde-bg;
|
||||
stroke-width: 1.5;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.cde-wheel-cran-label {
|
||||
font-family: "Averia Regular", sans-serif;
|
||||
font-size: 12px;
|
||||
fill: rgba(255,255,255,0.55);
|
||||
text-anchor: middle;
|
||||
dominant-baseline: central;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
// Combatant token circle on the wheel
|
||||
.cde-wheel-fighter-circle {
|
||||
stroke-width: 2;
|
||||
cursor: pointer;
|
||||
transition: r 0.2s, stroke-width 0.2s;
|
||||
|
||||
&:hover {
|
||||
stroke-width: 3;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
r: 18;
|
||||
stroke-width: 3;
|
||||
filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.7));
|
||||
}
|
||||
|
||||
&.is-turn {
|
||||
stroke-dasharray: 3 2;
|
||||
animation: cde-spin 4s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
.cde-wheel-fighter-initial {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
fill: #fff;
|
||||
text-anchor: middle;
|
||||
dominant-baseline: central;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Right: panel ---- */
|
||||
.cde-wheel-panel {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.cde-wheel-section-title {
|
||||
padding: 7px 12px 6px;
|
||||
border-bottom: 1px solid @cde-border;
|
||||
background: @cde-surface2;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: @cde-muted;
|
||||
border-left: 3px solid @cde-spell;
|
||||
flex-shrink: 0;
|
||||
|
||||
em {
|
||||
font-style: normal;
|
||||
color: @cde-text;
|
||||
text-transform: none;
|
||||
letter-spacing: 0;
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Combatant list */
|
||||
.cde-wheel-combatants {
|
||||
flex: 0 0 auto;
|
||||
max-height: 130px;
|
||||
overflow-y: auto;
|
||||
border-bottom: 1px solid @cde-border;
|
||||
}
|
||||
|
||||
.cde-wheel-combatant {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 12px 6px 9px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid fade(@cde-border, 50%);
|
||||
border-left: 3px solid transparent;
|
||||
transition: background 0.15s, border-color 0.15s;
|
||||
|
||||
&:last-child { border-bottom: none; }
|
||||
|
||||
&:hover {
|
||||
background: @cde-surface;
|
||||
}
|
||||
|
||||
&.cde-wheel-combatant--selected {
|
||||
background: fade(@cde-spell, 10%);
|
||||
border-left-color: @cde-spell;
|
||||
|
||||
.cde-wheel-combatant-name {
|
||||
color: @cde-text;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
&.cde-wheel-combatant--active {
|
||||
.cde-wheel-active-marker {
|
||||
color: #f0c040;
|
||||
filter: drop-shadow(0 0 3px #f0c040);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cde-wheel-combatant-img {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
border: 1.5px solid @cde-border-hi;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.cde-wheel-combatant-name {
|
||||
flex: 1 1 auto;
|
||||
font-size: 13px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: @cde-muted;
|
||||
}
|
||||
|
||||
.cde-wheel-combatant-cran {
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
font-variant-numeric: tabular-nums;
|
||||
flex-shrink: 0;
|
||||
min-width: 22px;
|
||||
text-align: center;
|
||||
padding: 2px 6px;
|
||||
border-radius: 12px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.cde-wheel-active-marker {
|
||||
font-size: 10px;
|
||||
color: @cde-muted;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Action area */
|
||||
.cde-wheel-actions {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
padding: 8px 10px 10px;
|
||||
gap: 6px;
|
||||
|
||||
&.cde-wheel-actions--hint {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.cde-wheel-hint {
|
||||
color: @cde-muted;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
i {
|
||||
font-size: 22px;
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
.cde-wheel-action-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.cde-wheel-action-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 4px;
|
||||
background: @cde-surface;
|
||||
border: 1px solid @cde-border-hi;
|
||||
border-radius: @cde-radius-sm;
|
||||
color: @cde-muted;
|
||||
font-size: 11px;
|
||||
padding: 5px 7px;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
transition: background 0.15s, border-color 0.15s, color 0.15s, box-shadow 0.15s;
|
||||
font-family: inherit;
|
||||
|
||||
&:hover {
|
||||
color: @cde-text;
|
||||
background: @cde-border;
|
||||
border-color: @cde-border-hi;
|
||||
}
|
||||
|
||||
.cde-wheel-action-name {
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cde-wheel-action-cost {
|
||||
flex-shrink: 0;
|
||||
font-weight: 700;
|
||||
font-size: 10px;
|
||||
padding: 1px 5px;
|
||||
border-radius: 10px;
|
||||
background: @cde-border-hi;
|
||||
color: @cde-muted;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
&[data-cost="1"] {
|
||||
&:hover { box-shadow: 0 0 6px fade(@cde-spell, 22%); }
|
||||
.cde-wheel-action-cost { background: #1a3d6a; color: #6aadff; }
|
||||
}
|
||||
&[data-cost="2"] {
|
||||
&:hover { box-shadow: 0 0 6px fade(#d4a050, 25%); }
|
||||
.cde-wheel-action-cost { background: #4a3200; color: #d4a050; }
|
||||
}
|
||||
&[data-cost="3"] {
|
||||
&:hover { box-shadow: 0 0 6px fade(#e06030, 28%); }
|
||||
.cde-wheel-action-cost { background: #4a1800; color: #e07840; }
|
||||
}
|
||||
&[data-cost="6"] {
|
||||
border-color: fade(#cc2040, 40%);
|
||||
&:hover { box-shadow: 0 0 6px fade(#cc2040, 35%); }
|
||||
.cde-wheel-action-cost { background: #4a0814; color: #e03050; }
|
||||
}
|
||||
}
|
||||
|
||||
/* Special action buttons */
|
||||
.cde-wheel-special-actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.cde-wheel-btn-roll,
|
||||
.cde-wheel-btn-surprise {
|
||||
flex: 1 1 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
border-radius: @cde-radius-sm;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
padding: 7px 8px;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
transition: background 0.15s, box-shadow 0.15s;
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
.cde-wheel-btn-roll {
|
||||
background: fade(#c08a00, 14%);
|
||||
border-color: fade(#c08a00, 50%);
|
||||
color: #e0b030;
|
||||
|
||||
&:hover {
|
||||
background: fade(#c08a00, 26%);
|
||||
box-shadow: 0 0 8px fade(#c08a00, 40%);
|
||||
}
|
||||
}
|
||||
|
||||
.cde-wheel-btn-surprise {
|
||||
background: fade(@cde-kungfu, 12%);
|
||||
border-color: fade(@cde-kungfu, 45%);
|
||||
color: @cde-kungfu;
|
||||
|
||||
&:hover {
|
||||
background: fade(@cde-kungfu, 24%);
|
||||
box-shadow: 0 0 8px fade(@cde-kungfu, 35%);
|
||||
}
|
||||
}
|
||||
|
||||
/* No-combat empty state */
|
||||
.cde-wheel-no-combat {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: @cde-muted;
|
||||
gap: 8px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
|
||||
i {
|
||||
font-size: 28px;
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
/* Spin animation for active-turn token */
|
||||
@keyframes cde-spin {
|
||||
from { transform-origin: var(--fx) var(--fy); transform: rotate(0deg); }
|
||||
to { transform-origin: var(--fx) var(--fy); transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* ===================================================================
|
||||
Migration App
|
||||
=================================================================== */
|
||||
|
||||
.cde-migration-app {
|
||||
.window-content {
|
||||
padding: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.cde-migration-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
/* Drop zone */
|
||||
.cde-migration-drop-zone {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 28px 20px;
|
||||
border: 2px dashed @cde-border;
|
||||
border-radius: 8px;
|
||||
background: fadeout(@cde-surface2, 40%);
|
||||
text-align: center;
|
||||
transition: border-color 0.15s, background 0.15s;
|
||||
cursor: pointer;
|
||||
|
||||
&.is-dragover {
|
||||
border-color: @cde-spell;
|
||||
background: fadeout(@cde-spell, 85%);
|
||||
}
|
||||
}
|
||||
|
||||
.cde-migration-drop-icon {
|
||||
font-size: 36px;
|
||||
color: @cde-spell;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.cde-migration-drop-hint {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: @cde-muted;
|
||||
}
|
||||
|
||||
.cde-migration-file-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 14px;
|
||||
border: 1px solid @cde-spell;
|
||||
border-radius: 4px;
|
||||
color: @cde-spell;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
|
||||
&:hover {
|
||||
background: fadeout(@cde-spell, 80%);
|
||||
}
|
||||
}
|
||||
|
||||
/* Preview section */
|
||||
.cde-migration-preview {
|
||||
border: 1px solid @cde-border;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cde-migration-preview-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 12px;
|
||||
background: fadeout(@cde-surface2, 20%);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: @cde-muted;
|
||||
}
|
||||
|
||||
.cde-migration-clear-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 2px 8px;
|
||||
border: 1px solid @cde-border;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
color: @cde-muted;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
transition: color 0.15s, border-color 0.15s;
|
||||
|
||||
&:hover {
|
||||
color: #e04444;
|
||||
border-color: #e04444;
|
||||
}
|
||||
}
|
||||
|
||||
/* Preview table */
|
||||
.cde-migration-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 12px;
|
||||
|
||||
th {
|
||||
padding: 5px 8px;
|
||||
background: fadeout(@cde-surface2, 10%);
|
||||
color: @cde-muted;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid @cde-border;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 5px 8px;
|
||||
border-bottom: 1px solid fadeout(@cde-border, 60%);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.cde-migration-thumb {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 3px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.cde-migration-name {
|
||||
font-weight: 600;
|
||||
color: @cde-text;
|
||||
}
|
||||
|
||||
.cde-migration-items-count {
|
||||
text-align: center;
|
||||
color: @cde-muted;
|
||||
}
|
||||
|
||||
.cde-migration-srcfile {
|
||||
font-size: 10px;
|
||||
color: @cde-muted;
|
||||
max-width: 130px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Type badge */
|
||||
.cde-migration-type-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
|
||||
&.cde-migration-type-character {
|
||||
background: fadeout(@cde-spell, 80%);
|
||||
color: @cde-spell;
|
||||
border: 1px solid fadeout(@cde-spell, 60%);
|
||||
}
|
||||
|
||||
&.cde-migration-type-npc {
|
||||
background: fadeout(#9c4dcc, 80%);
|
||||
color: #c97ae0;
|
||||
border: 1px solid fadeout(#9c4dcc, 60%);
|
||||
}
|
||||
}
|
||||
|
||||
/* Errors */
|
||||
.cde-migration-errors {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 6px;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid fadeout(#e04444, 40%);
|
||||
border-radius: 4px;
|
||||
background: fadeout(#e04444, 90%);
|
||||
color: #e07070;
|
||||
font-size: 11px;
|
||||
|
||||
i { margin-top: 2px; flex-shrink: 0; }
|
||||
}
|
||||
}
|
||||
|
||||
/* Bottom action bar */
|
||||
.cde-migration-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.cde-migration-import-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 9px 24px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
background: @cde-spell;
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: filter 0.15s;
|
||||
|
||||
&:hover {
|
||||
filter: brightness(1.15);
|
||||
}
|
||||
}
|
||||
|
||||
.cde-migration-hint {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: @cde-muted;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Welcome message
|
||||
// ============================================================
|
||||
|
||||
.cde-welcome-message {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 16px;
|
||||
background: @cde-surface;
|
||||
border: 1px solid @cde-border-hi;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cde-welcome-logo {
|
||||
width: 120px;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
filter: drop-shadow(0 0 8px fade(@cde-spell, 40%));
|
||||
}
|
||||
|
||||
.cde-welcome-title {
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: @cde-spell;
|
||||
text-shadow: 0 0 8px fade(@cde-spell, 50%);
|
||||
}
|
||||
|
||||
.cde-welcome-links {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: @cde-muted;
|
||||
|
||||
a {
|
||||
color: @cde-item;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px solid fade(@cde-item, 40%);
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
border-bottom-color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cde-welcome-help-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-top: 4px;
|
||||
padding: 7px 18px;
|
||||
background: @cde-spell;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: filter 0.15s;
|
||||
|
||||
&:hover {
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,11 +130,460 @@ var TEMPLATE_PARTIALS = [
|
||||
"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-npc-kungfus.html",
|
||||
"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-npc-items.html",
|
||||
"systems/fvtt-chroniques-de-l-etrange/templates/apps/cde-loksyu-app.html",
|
||||
"systems/fvtt-chroniques-de-l-etrange/templates/apps/cde-tinji-app.html"
|
||||
"systems/fvtt-chroniques-de-l-etrange/templates/apps/cde-tinji-app.html",
|
||||
"systems/fvtt-chroniques-de-l-etrange/templates/apps/cde-wheel-app.html"
|
||||
];
|
||||
|
||||
// src/migration/migrator.js
|
||||
var ELEMENT_LABEL_TO_KEY = {
|
||||
"m\xE9tal": "metal",
|
||||
"metal": "metal",
|
||||
"eau": "eau",
|
||||
"terre": "terre",
|
||||
"feu": "feu",
|
||||
"bois": "bois"
|
||||
};
|
||||
function elementKey(label = "") {
|
||||
return ELEMENT_LABEL_TO_KEY[label.toLowerCase().trim()] ?? "metal";
|
||||
}
|
||||
function heiKey(label = "") {
|
||||
const l = label.toLowerCase().trim();
|
||||
if (l === "yin/yang" || l === "yinyang") return "yinyang";
|
||||
if (l === "yang") return "yang";
|
||||
return "yin";
|
||||
}
|
||||
var SPECIALITY_TO_DISCIPLINE = {
|
||||
// internalcinnabar
|
||||
"essence": "internalcinnabar",
|
||||
"esprit": "internalcinnabar",
|
||||
"mind": "internalcinnabar",
|
||||
"purification": "internalcinnabar",
|
||||
"manipulation": "internalcinnabar",
|
||||
"aura": "internalcinnabar",
|
||||
// alchemy
|
||||
"acupuncture": "alchemy",
|
||||
"\xE9lixirs": "alchemy",
|
||||
"elixirs": "alchemy",
|
||||
"poisons": "alchemy",
|
||||
"arsenal": "alchemy",
|
||||
"potions": "alchemy",
|
||||
// masteryoftheway
|
||||
"mal\xE9diction": "masteryoftheway",
|
||||
"malediction": "masteryoftheway",
|
||||
"transfiguration": "masteryoftheway",
|
||||
"n\xE9cromancie": "masteryoftheway",
|
||||
"necromancie": "masteryoftheway",
|
||||
"contr\xF4le climatique": "masteryoftheway",
|
||||
"controle climatique": "masteryoftheway",
|
||||
"magie d'or": "masteryoftheway",
|
||||
"magie dor": "masteryoftheway",
|
||||
// exorcism
|
||||
"invocation": "exorcism",
|
||||
"pistage": "exorcism",
|
||||
"tra\xE7age": "exorcism",
|
||||
"tracage": "exorcism",
|
||||
"protection": "exorcism",
|
||||
"ch\xE2timent": "exorcism",
|
||||
"chatiment": "exorcism",
|
||||
"domination": "exorcism",
|
||||
// geomancy
|
||||
"neutralisation": "geomancy",
|
||||
"divination": "geomancy",
|
||||
"pri\xE8re terrestre": "geomancy",
|
||||
"priere terrestre": "geomancy",
|
||||
"pri\xE8re c\xE9leste": "geomancy",
|
||||
"priere celeste": "geomancy",
|
||||
"g\xE9omancie": "geomancy",
|
||||
"geomancie": "geomancy",
|
||||
"feng shui": "geomancy",
|
||||
"fungseoi": "geomancy"
|
||||
};
|
||||
function inferDiscipline(specialityName = "", itemName = "") {
|
||||
const key = specialityName.toLowerCase().trim();
|
||||
if (SPECIALITY_TO_DISCIPLINE[key]) return SPECIALITY_TO_DISCIPLINE[key];
|
||||
const name = itemName.toLowerCase();
|
||||
if (name.includes("exorcis")) return "exorcism";
|
||||
if (name.includes("g\xE9omanci") || name.includes("geomanci")) return "geomancy";
|
||||
if (name.includes("alchimi")) return "alchemy";
|
||||
if (name.includes("cinnabre") || name.includes("interne")) return "internalcinnabar";
|
||||
if (name.includes("ma\xEEtrise") || name.includes("maitrise") || name.includes("tao")) return "masteryoftheway";
|
||||
return "internalcinnabar";
|
||||
}
|
||||
function mapActivation(oldActivation = "") {
|
||||
const s = oldActivation.toLowerCase();
|
||||
if (s.includes("inflig\xE9s") || s.includes("infliges")) return "damage-inflicted";
|
||||
if (s.includes("re\xE7us") || s.includes("recus")) return "damage-received";
|
||||
if (s.includes("r\xE9action") || s.includes("reaction")) return "reaction";
|
||||
if (s.includes("d\xE9s-fastes") || s.includes("des-fastes") || s.includes("fastes")) return "dice";
|
||||
if (s.includes("aide")) return "action-aid";
|
||||
if (s.includes("attaque") && s.includes("d\xE9fense")) return "action-attack-defense";
|
||||
if (s.includes("attaque") && s.includes("defense")) return "action-attack-defense";
|
||||
if (s.includes("attaque")) return "action-attack";
|
||||
if (s.includes("d\xE9fense") || s.includes("defense")) return "action-defense";
|
||||
return "action-attack";
|
||||
}
|
||||
var DEFAULT_ACTOR_IMG = "icons/svg/mystery-man.svg";
|
||||
var DEFAULT_ITEM_IMG = "icons/svg/item-bag.svg";
|
||||
function migrateEquipmentItem(oldItem) {
|
||||
const s = oldItem.system ?? {};
|
||||
return {
|
||||
name: oldItem.name,
|
||||
type: "item",
|
||||
img: oldItem.img || DEFAULT_ITEM_IMG,
|
||||
system: {
|
||||
reference: s.reference ?? "",
|
||||
description: s.description ?? "",
|
||||
quantity: Number(s.quantity ?? 1),
|
||||
weight: Number(s.weight ?? 0),
|
||||
notes: s.notes ?? ""
|
||||
}
|
||||
};
|
||||
}
|
||||
function migrateKungfuItem(oldItem) {
|
||||
const s = oldItem.system ?? {};
|
||||
const techs = s.techniques ?? {};
|
||||
const migratedTechs = {};
|
||||
for (const key of ["technique1", "technique2", "technique3"]) {
|
||||
const t = techs[key] ?? {};
|
||||
migratedTechs[key] = {
|
||||
check: Boolean(t.check),
|
||||
name: t.name ?? "",
|
||||
activation: mapActivation(t.activation ?? ""),
|
||||
technique: t.technique ?? ""
|
||||
};
|
||||
}
|
||||
return {
|
||||
name: oldItem.name,
|
||||
type: "kungfu",
|
||||
img: oldItem.img || DEFAULT_ITEM_IMG,
|
||||
system: {
|
||||
reference: s.reference ?? "",
|
||||
description: s.description ?? "",
|
||||
orientation: s.orientation || "yin",
|
||||
aspect: s.aspect || "metal",
|
||||
skill: s.skill || "kungfu",
|
||||
speciality: s.speciality ?? "",
|
||||
style: s.style ?? "",
|
||||
techniques: migratedTechs,
|
||||
notes: s.notes ?? ""
|
||||
}
|
||||
};
|
||||
}
|
||||
function migrateSpellItem(oldItem) {
|
||||
const s = oldItem.system ?? {};
|
||||
return {
|
||||
name: oldItem.name,
|
||||
type: "spell",
|
||||
img: oldItem.img || DEFAULT_ITEM_IMG,
|
||||
system: {
|
||||
reference: s.reference ?? "",
|
||||
description: s.description ?? "",
|
||||
specialityname: s.specialityname ?? "",
|
||||
associatedelement: elementKey(s.associatedelement ?? ""),
|
||||
heiType: heiKey(s.hei ?? ""),
|
||||
heiCost: Number(s.heiCost ?? 0),
|
||||
difficulty: Number(s.difficulty ?? 0),
|
||||
realizationtimeritual: s.realizationtimeritual ?? "",
|
||||
realizationtimeaccelerated: s.realizationtimeaccelerated ?? "",
|
||||
flashback: s.flashback ?? "",
|
||||
components: s.components ?? "",
|
||||
effects: s.effects ?? "",
|
||||
examples: s.examples ?? "",
|
||||
notes: s.notes ?? "",
|
||||
discipline: inferDiscipline(s.specialityname ?? "", oldItem.name ?? "")
|
||||
}
|
||||
};
|
||||
}
|
||||
function migrateSupernaturalItem(oldItem) {
|
||||
const s = oldItem.system ?? {};
|
||||
const nestedRef = s.supernatural?.reference ?? "";
|
||||
return {
|
||||
name: oldItem.name,
|
||||
type: "supernatural",
|
||||
img: oldItem.img || DEFAULT_ITEM_IMG,
|
||||
system: {
|
||||
reference: s.reference || nestedRef,
|
||||
description: s.description ?? "",
|
||||
notes: s.notes ?? "",
|
||||
heiType: "yin",
|
||||
heiCost: 0,
|
||||
trigger: "",
|
||||
effects: ""
|
||||
}
|
||||
};
|
||||
}
|
||||
function migrateItem(oldItem) {
|
||||
switch (oldItem.type) {
|
||||
case "item":
|
||||
return migrateEquipmentItem(oldItem);
|
||||
case "kungfu":
|
||||
return migrateKungfuItem(oldItem);
|
||||
case "spell":
|
||||
return migrateSpellItem(oldItem);
|
||||
case "supernatural":
|
||||
return migrateSupernaturalItem(oldItem);
|
||||
default:
|
||||
return migrateEquipmentItem({ ...oldItem, type: "item" });
|
||||
}
|
||||
}
|
||||
function migrateCharacter(old) {
|
||||
const s = old.system ?? {};
|
||||
const aspect = {};
|
||||
for (const [k, v] of Object.entries(s.aspect ?? {})) {
|
||||
aspect[k] = { chinese: v.chinese ?? "", label: v.label ?? "", value: Number(v.value ?? 0) };
|
||||
}
|
||||
const skills = {};
|
||||
for (const [k, v] of Object.entries(s.skills ?? {})) {
|
||||
skills[k] = { label: v.label ?? "", specialities: v.specialities ?? "", value: Number(v.value ?? 0) };
|
||||
}
|
||||
const resources = {};
|
||||
for (const [k, v] of Object.entries(s.resources ?? {})) {
|
||||
resources[k] = { label: v.label ?? "", specialities: v.specialities ?? "", value: Number(v.value ?? 0), debt: Boolean(v.debt) };
|
||||
}
|
||||
const component = {};
|
||||
for (const [k, v] of Object.entries(s.component ?? {})) {
|
||||
component[k] = { value: v.value ?? "" };
|
||||
}
|
||||
const MAGIC_SPECIALITIES = {
|
||||
internalcinnabar: ["essence", "mind", "purification", "manipulation", "aura"],
|
||||
alchemy: ["acupuncture", "elixirs", "poisons", "arsenal", "potions"],
|
||||
masteryoftheway: ["curse", "transfiguration", "necromancy", "climatecontrol", "goldenmagic"],
|
||||
exorcism: ["invocation", "tracking", "protection", "punishment", "domination"],
|
||||
geomancy: ["neutralization", "divination", "earthlyprayer", "heavenlyprayer", "fungseoi"]
|
||||
};
|
||||
const magics = {};
|
||||
for (const [school, specs] of Object.entries(MAGIC_SPECIALITIES)) {
|
||||
const om = s.magics?.[school] ?? {};
|
||||
const speciality = {};
|
||||
for (const spec of specs) {
|
||||
speciality[spec] = { check: Boolean(om.speciality?.[spec]?.check) };
|
||||
}
|
||||
magics[school] = { visible: Boolean(om.visible), value: Number(om.value ?? 0), speciality };
|
||||
}
|
||||
const tt = s.threetreasures ?? {};
|
||||
const threetreasures = {
|
||||
heiyang: { value: Number(tt.heiyang?.value ?? 0), max: Number(tt.heiyang?.max ?? 0) },
|
||||
heiyin: { value: Number(tt.heiyin?.value ?? 0), max: Number(tt.heiyin?.max ?? 0) },
|
||||
dicelevel: {
|
||||
level0d: {
|
||||
san: { value: Number(tt.dicelevel?.level0d?.san?.value ?? 0), max: Number(tt.dicelevel?.level0d?.san?.max ?? 0) },
|
||||
zing: { value: Number(tt.dicelevel?.level0d?.zing?.value ?? 0), max: Number(tt.dicelevel?.level0d?.zing?.max ?? 0) }
|
||||
},
|
||||
level1d: {
|
||||
san: { value: Number(tt.dicelevel?.level1d?.san?.value ?? 0), max: Number(tt.dicelevel?.level1d?.san?.max ?? 0) },
|
||||
zing: { value: Number(tt.dicelevel?.level1d?.zing?.value ?? 0), max: Number(tt.dicelevel?.level1d?.zing?.max ?? 0) }
|
||||
},
|
||||
level2d: {
|
||||
san: { value: Number(tt.dicelevel?.level2d?.san?.value ?? 0), max: Number(tt.dicelevel?.level2d?.san?.max ?? 0) },
|
||||
zing: { value: Number(tt.dicelevel?.level2d?.zing?.value ?? 0), max: Number(tt.dicelevel?.level2d?.zing?.max ?? 0) }
|
||||
}
|
||||
}
|
||||
};
|
||||
const description = s.description || s.biography || "";
|
||||
return {
|
||||
name: old.name,
|
||||
type: "character",
|
||||
img: old.img || DEFAULT_ACTOR_IMG,
|
||||
system: {
|
||||
concept: s.concept ?? "",
|
||||
guardian: parseInt(s.guardian ?? "0") || 0,
|
||||
initiative: Number(s.initiative ?? 1),
|
||||
anti_initiative: Number(s.anti_initiative ?? 24),
|
||||
description,
|
||||
aspect,
|
||||
skills,
|
||||
resources,
|
||||
component,
|
||||
magics,
|
||||
threetreasures,
|
||||
experience: {
|
||||
value: Number(s.experience?.value ?? 0),
|
||||
max: Number(s.experience?.max ?? 0),
|
||||
min: Number(s.experience?.min ?? 0)
|
||||
}
|
||||
},
|
||||
items: (old.items ?? []).map(migrateItem)
|
||||
};
|
||||
}
|
||||
function migrateNpc(old) {
|
||||
const s = old.system ?? {};
|
||||
const aptitudes = {};
|
||||
for (const [k, v] of Object.entries(s.aptitudes ?? {})) {
|
||||
aptitudes[k] = { value: Number(v.value ?? 0), speciality: v.speciality ?? "" };
|
||||
}
|
||||
return {
|
||||
name: old.name,
|
||||
type: "npc",
|
||||
img: old.img || DEFAULT_ACTOR_IMG,
|
||||
system: {
|
||||
type: s.type ?? "",
|
||||
// Old system had separate `levelofthreat`/`powerofnuisance` as numbers
|
||||
// and string copies `threat`/`nuisance` — use the numeric fields
|
||||
threat: Number(s.levelofthreat ?? s.threat ?? 0),
|
||||
nuisance: Number(s.powerofnuisance ?? s.nuisance ?? 0),
|
||||
initiative: Number(s.initiative ?? 1),
|
||||
anti_initiative: Number(s.anti_initiative ?? 24),
|
||||
aptitudes,
|
||||
vitality: {
|
||||
value: Number(s.vitality?.value ?? 0),
|
||||
calcul: Number(s.vitality?.calcul ?? 0),
|
||||
note: s.vitality?.note ?? ""
|
||||
},
|
||||
hei: {
|
||||
value: Number(s.hei?.value ?? 0),
|
||||
calcul: Number(s.hei?.calcul ?? 0),
|
||||
note: s.hei?.note ?? ""
|
||||
},
|
||||
description: s.description ?? ""
|
||||
},
|
||||
items: (old.items ?? []).map(migrateItem)
|
||||
};
|
||||
}
|
||||
function migrateActor(oldJson) {
|
||||
switch (oldJson.type) {
|
||||
case "character":
|
||||
return migrateCharacter(oldJson);
|
||||
case "npc":
|
||||
return migrateNpc(oldJson);
|
||||
default:
|
||||
throw new Error(`Unknown actor type "${oldJson.type}" in "${oldJson.name}"`);
|
||||
}
|
||||
}
|
||||
function parseLegacyJson(jsonText) {
|
||||
const parsed = JSON.parse(jsonText);
|
||||
if (typeof parsed !== "object" || parsed === null) {
|
||||
throw new Error("Le fichier JSON doit contenir un objet acteur ou un tableau d'acteurs");
|
||||
}
|
||||
const actors = Array.isArray(parsed) ? parsed : [parsed];
|
||||
return actors.map(migrateActor);
|
||||
}
|
||||
|
||||
// src/ui/apps/migration-app.js
|
||||
var MIGRATION_TEMPLATE = "systems/fvtt-chroniques-de-l-etrange/templates/apps/cde-migration-app.html";
|
||||
var CDEMigrationApp = class _CDEMigrationApp extends foundry.applications.api.HandlebarsApplicationMixin(
|
||||
foundry.applications.api.ApplicationV2
|
||||
) {
|
||||
static DEFAULT_OPTIONS = {
|
||||
id: "cde-migration-app",
|
||||
classes: ["cde-migration-app"],
|
||||
tag: "div",
|
||||
window: {
|
||||
title: "CDE.MigrationTitle",
|
||||
icon: "fas fa-file-import",
|
||||
resizable: false
|
||||
},
|
||||
position: { width: 560, height: "auto" },
|
||||
actions: {
|
||||
clearFiles: _CDEMigrationApp.#clearFiles,
|
||||
doImport: _CDEMigrationApp.#doImport
|
||||
}
|
||||
};
|
||||
static PARTS = {
|
||||
form: { template: MIGRATION_TEMPLATE }
|
||||
};
|
||||
/** @type {Array<{name: string, type: string, img: string, system: object, items: object[], _srcFile: string}>} */
|
||||
#pending = [];
|
||||
/** @type {string[]} - error messages per file */
|
||||
#errors = [];
|
||||
async _prepareContext(options) {
|
||||
return {
|
||||
pending: this.#pending,
|
||||
errors: this.#errors,
|
||||
hasPending: this.#pending.length > 0,
|
||||
hasErrors: this.#errors.length > 0,
|
||||
count: this.#pending.length
|
||||
};
|
||||
}
|
||||
/** After render, wire up the file input. */
|
||||
_onRender(context, options) {
|
||||
super._onRender(context, options);
|
||||
const input = this.element.querySelector(".cde-migration-file-input");
|
||||
input?.addEventListener("change", this.#onFileChange.bind(this));
|
||||
const dropZone = this.element.querySelector(".cde-migration-drop-zone");
|
||||
if (dropZone) {
|
||||
dropZone.addEventListener("dragover", (e) => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.add("is-dragover");
|
||||
});
|
||||
dropZone.addEventListener("dragleave", () => dropZone.classList.remove("is-dragover"));
|
||||
dropZone.addEventListener("drop", (e) => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.remove("is-dragover");
|
||||
this.#processFiles(Array.from(e.dataTransfer.files));
|
||||
});
|
||||
}
|
||||
}
|
||||
async #onFileChange(event) {
|
||||
const files = Array.from(event.target.files ?? []);
|
||||
event.target.value = "";
|
||||
await this.#processFiles(files);
|
||||
}
|
||||
async #processFiles(files) {
|
||||
for (const file of files) {
|
||||
if (!file.name.endsWith(".json")) {
|
||||
this.#errors.push(game.i18n.format("CDE.MigrationErrorNotJson", { file: file.name }));
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const text = await file.text();
|
||||
const actors = parseLegacyJson(text);
|
||||
for (const actor of actors) {
|
||||
actor._srcFile = file.name;
|
||||
if (!this.#pending.some((p) => p.name === actor.name)) {
|
||||
this.#pending.push(actor);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
this.#errors.push(game.i18n.format("CDE.MigrationErrorParse", { file: file.name, error: err.message }));
|
||||
}
|
||||
}
|
||||
this.render();
|
||||
}
|
||||
static async #clearFiles() {
|
||||
this.#pending = [];
|
||||
this.#errors = [];
|
||||
this.render();
|
||||
}
|
||||
static async #doImport() {
|
||||
if (!this.#pending.length) return;
|
||||
const created = [];
|
||||
const failed = [];
|
||||
for (const data of this.#pending) {
|
||||
try {
|
||||
const { _srcFile, ...actorData } = data;
|
||||
const actor = await Actor.create(actorData);
|
||||
created.push(actor.name);
|
||||
} catch (err) {
|
||||
failed.push(`${data.name}: ${err.message}`);
|
||||
console.error(`CHRONIQUESDELETRANGE | Migration failed for "${data.name}":`, err);
|
||||
}
|
||||
}
|
||||
this.#pending = [];
|
||||
this.#errors = failed;
|
||||
this.render();
|
||||
if (created.length) {
|
||||
ui.notifications.info(
|
||||
game.i18n.format("CDE.MigrationSuccess", { count: created.length, names: created.join(", ") })
|
||||
);
|
||||
}
|
||||
if (failed.length) {
|
||||
ui.notifications.warn(
|
||||
game.i18n.format("CDE.MigrationPartialError", { count: failed.length })
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// src/config/settings.js
|
||||
function registerSettings() {
|
||||
game.settings.registerMenu(SYSTEM_ID, "migrationTool", {
|
||||
name: "CDE.MigrationTitle",
|
||||
label: "CDE.MigrationMenuLabel",
|
||||
hint: "CDE.MigrationMenuHint",
|
||||
icon: "fas fa-file-import",
|
||||
type: CDEMigrationApp,
|
||||
restricted: true
|
||||
});
|
||||
game.settings.register(SYSTEM_ID, "loksyuData", {
|
||||
scope: "world",
|
||||
config: false,
|
||||
@@ -153,9 +602,36 @@ function registerSettings() {
|
||||
type: Number,
|
||||
default: 0
|
||||
});
|
||||
game.settings.register(SYSTEM_ID, "welcomeSceneLoaded", {
|
||||
scope: "world",
|
||||
config: false,
|
||||
type: Boolean,
|
||||
default: false
|
||||
});
|
||||
}
|
||||
async function migrateIfNeeded() {
|
||||
}
|
||||
async function loadWelcomeSceneIfNeeded() {
|
||||
if (!game.user.isGM) return;
|
||||
if (game.settings.get(SYSTEM_ID, "welcomeSceneLoaded")) return;
|
||||
try {
|
||||
const pack = game.packs.get(`${SYSTEM_ID}.cde-scenes`);
|
||||
if (!pack) return;
|
||||
const index = await pack.getIndex();
|
||||
const entry = index.find((e) => e.name === "Accueil");
|
||||
if (!entry) return;
|
||||
const existing = game.scenes.find((s) => s.name === "Accueil");
|
||||
let scene = existing;
|
||||
if (!scene) {
|
||||
const doc = await pack.getDocument(entry._id);
|
||||
[scene] = await Scene.createDocuments([doc.toObject()]);
|
||||
}
|
||||
await game.settings.set(SYSTEM_ID, "welcomeSceneLoaded", true);
|
||||
await scene.activate();
|
||||
} catch (err) {
|
||||
console.error("CHRONIQUESDELETRANGE | loadWelcomeSceneIfNeeded failed:", err);
|
||||
}
|
||||
}
|
||||
|
||||
// src/config/localize.js
|
||||
function preLocalizeConfig() {
|
||||
@@ -648,7 +1124,25 @@ var CDEActor = class extends Actor {
|
||||
};
|
||||
|
||||
// src/documents/item.js
|
||||
var DEFAULT_ICONS = {
|
||||
kungfu: "systems/fvtt-chroniques-de-l-etrange/images/icons/icon-kungfu.svg",
|
||||
spell: "systems/fvtt-chroniques-de-l-etrange/images/icons/icon-spell.svg",
|
||||
supernatural: "systems/fvtt-chroniques-de-l-etrange/images/icons/icon-supernatural.svg",
|
||||
weapon: "systems/fvtt-chroniques-de-l-etrange/images/icons/icon-weapon.svg",
|
||||
armor: "systems/fvtt-chroniques-de-l-etrange/images/icons/icon-armor.svg",
|
||||
sanhei: "systems/fvtt-chroniques-de-l-etrange/images/icons/icon-sanhei.svg",
|
||||
ingredient: "systems/fvtt-chroniques-de-l-etrange/images/icons/icon-ingredient.svg",
|
||||
item: "systems/fvtt-chroniques-de-l-etrange/images/icons/icon-item.svg"
|
||||
};
|
||||
var CDEItem = class extends Item {
|
||||
/** @override */
|
||||
async _preCreate(data, options, userId) {
|
||||
await super._preCreate(data, options, userId);
|
||||
const defaultIcon = DEFAULT_ICONS[this.type];
|
||||
if (defaultIcon && (!data.img || data.img === Item.DEFAULT_ICON)) {
|
||||
this.updateSource({ img: defaultIcon });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// src/ui/dice.js
|
||||
@@ -789,6 +1283,20 @@ function registerHandlebarsHelpers() {
|
||||
};
|
||||
return game.i18n.localize(keys[activation] ?? "CDE.Activation");
|
||||
});
|
||||
Handlebars.registerHelper("cranPosition", function(cran, cx, cy, r) {
|
||||
const angleDeg = 90 + cran * 15;
|
||||
const angleRad = angleDeg * Math.PI / 180;
|
||||
const x = Math.round(cx + r * Math.cos(angleRad));
|
||||
const y = Math.round(cy - r * Math.sin(angleRad));
|
||||
return { x, y };
|
||||
});
|
||||
Handlebars.registerHelper("fighterX", function(cx, index, total) {
|
||||
const offset = total > 1 ? (index - (total - 1) / 2) * 34 : 0;
|
||||
return Math.round(cx - 15 + offset);
|
||||
});
|
||||
Handlebars.registerHelper("fighterY", function(cy, index, total) {
|
||||
return Math.round(cy - 50);
|
||||
});
|
||||
}
|
||||
|
||||
// src/ui/templates.js
|
||||
@@ -1622,6 +2130,7 @@ var CDECharacterSheet = class extends CDEBaseActorSheet {
|
||||
this.#bindPrefs();
|
||||
this.#bindRollButtons();
|
||||
this.#bindComponentRandomize();
|
||||
this.#bindDiagramZoom();
|
||||
}
|
||||
#bindInitiativeControls() {
|
||||
const buttons = this.element?.querySelectorAll(".click-initiative");
|
||||
@@ -1700,6 +2209,16 @@ var CDECharacterSheet = class extends CDEBaseActorSheet {
|
||||
});
|
||||
});
|
||||
}
|
||||
#bindDiagramZoom() {
|
||||
const img = this.element?.querySelector("[data-action='zoom-diagram']");
|
||||
if (!img) return;
|
||||
img.addEventListener("click", () => {
|
||||
new ImagePopout(img.src, {
|
||||
title: game.i18n.localize("CDE.NghangDiagramTitle"),
|
||||
shareable: false
|
||||
}).render(true);
|
||||
});
|
||||
}
|
||||
#bindComponentRandomize() {
|
||||
const btn = this.element?.querySelector("[data-action='randomize-component']");
|
||||
if (!btn) return;
|
||||
@@ -1992,7 +2511,8 @@ var CDELoksyuApp = class _CDELoksyuApp extends foundry.applications.api.Handleba
|
||||
position: { width: 520, height: "auto" },
|
||||
actions: {
|
||||
resetElement: _CDELoksyuApp.#onResetElement,
|
||||
resetAll: _CDELoksyuApp.#onResetAll
|
||||
resetAll: _CDELoksyuApp.#onResetAll,
|
||||
zoomVisual: _CDELoksyuApp.#onZoomVisual
|
||||
}
|
||||
};
|
||||
static PARTS = {
|
||||
@@ -2075,6 +2595,12 @@ var CDELoksyuApp = class _CDELoksyuApp extends foundry.applications.api.Handleba
|
||||
for (const k of KEYS) data[k] = { yin: 0, yang: 0 };
|
||||
await setLoksyuData(data);
|
||||
}
|
||||
static #onZoomVisual(_event, target) {
|
||||
new ImagePopout(target.src, {
|
||||
title: game.i18n.localize("CDE.LoksyuDiagramTitle"),
|
||||
shareable: false
|
||||
}).render(true);
|
||||
}
|
||||
};
|
||||
|
||||
// src/ui/apps/tinji-app.js
|
||||
@@ -2090,7 +2616,7 @@ var CDETinjiApp = class _CDETinjiApp extends foundry.applications.api.Handlebars
|
||||
resizable: false
|
||||
},
|
||||
classes: ["cde-app", "cde-tinji-standalone"],
|
||||
position: { width: 320, height: "auto" },
|
||||
position: { width: 380, height: "auto" },
|
||||
actions: {
|
||||
increment: _CDETinjiApp.#onIncrement,
|
||||
decrement: _CDETinjiApp.#onDecrement,
|
||||
@@ -2173,6 +2699,205 @@ var CDETinjiApp = class _CDETinjiApp extends foundry.applications.api.Handlebars
|
||||
}
|
||||
};
|
||||
|
||||
// src/documents/combat.js
|
||||
var CDECombat = class extends Combat {
|
||||
/**
|
||||
* Override rollInitiative to open the PC or NPC initiative dialog
|
||||
* for each selected combatant, then sync the result to the Combatant document.
|
||||
*/
|
||||
async rollInitiative(ids, options = {}) {
|
||||
const combatantIds = typeof ids === "string" ? [ids] : ids;
|
||||
for (const id of combatantIds) {
|
||||
const combatant = this.combatants.get(id);
|
||||
if (!combatant) continue;
|
||||
const actor = combatant.actor;
|
||||
if (!actor) continue;
|
||||
if (actor.type === ACTOR_TYPES.character) {
|
||||
await rollInitiativePC(actor);
|
||||
} else {
|
||||
await rollInitiativeNPC(actor);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Sort combatants: highest initiative first (furthest counter-clockwise = acts first).
|
||||
* Ties: PCs before NPCs; among PCs, by name; among NPCs, by name.
|
||||
* Calls super.setupTurns() first to ensure this.current is properly initialized.
|
||||
*/
|
||||
setupTurns() {
|
||||
super.setupTurns();
|
||||
this.turns = this.turns.slice().sort((a, b) => {
|
||||
const ia = a.initiative ?? 0;
|
||||
const ib = b.initiative ?? 0;
|
||||
if (ia !== ib) return ib - ia;
|
||||
const aIsPC = a.actor?.type === ACTOR_TYPES.character ? 1 : 0;
|
||||
const bIsPC = b.actor?.type === ACTOR_TYPES.character ? 1 : 0;
|
||||
if (aIsPC !== bIsPC) return bIsPC - aIsPC;
|
||||
return (a.name ?? "").localeCompare(b.name ?? "");
|
||||
});
|
||||
return this.turns;
|
||||
}
|
||||
};
|
||||
async function advanceCombatantPosition(combatant, cranCost) {
|
||||
const current = combatant.initiative ?? combatant.actor?.system?.initiative ?? 1;
|
||||
const newValue = (current - cranCost - 1 + 48) % 24 + 1;
|
||||
await combatant.update({ initiative: newValue });
|
||||
}
|
||||
|
||||
// src/ui/apps/wheel-app.js
|
||||
var WHEEL_TEMPLATE = "systems/fvtt-chroniques-de-l-etrange/templates/apps/cde-wheel-app.html";
|
||||
var ACTION_COSTS = [
|
||||
{ key: "draw", label: "CDE.ActionCostDraw", cost: 1 },
|
||||
{ key: "changestyle", label: "CDE.ActionCostChangeStyle", cost: 1 },
|
||||
{ key: "defense", label: "CDE.ActionCostDefense", cost: 1 },
|
||||
{ key: "aim", label: "CDE.ActionCostAim", cost: 2 },
|
||||
{ key: "help", label: "CDE.ActionCostHelp", cost: 2 },
|
||||
{ key: "defally", label: "CDE.ActionCostDefendAlly", cost: 2 },
|
||||
{ key: "move", label: "CDE.ActionCostMove", cost: 2 },
|
||||
{ key: "attack", label: "CDE.ActionCostAttack", cost: 3 },
|
||||
{ key: "delay", label: "CDE.ActionCostDelay", cost: 6 }
|
||||
];
|
||||
var WHEEL_SEGMENTS = [
|
||||
{ label: "M\xE9tal", color: "#b8c4cc", textColor: "#1a1a1a", crans: [1, 2, 3, 4] },
|
||||
{ label: "Eau", color: "#3a7bd5", textColor: "#ffffff", crans: [5, 6, 7, 8] },
|
||||
{ label: "Terre", color: "#c8a84b", textColor: "#1a1a1a", crans: [9, 10, 11, 12] },
|
||||
{ label: "Feu", color: "#d94f3d", textColor: "#ffffff", crans: [13, 14, 15, 16] },
|
||||
{ label: "Bois", color: "#4a9b5a", textColor: "#ffffff", crans: [17, 18, 19, 20] },
|
||||
{ label: "Rep\xE8re", color: "#1a1a2e", textColor: "#aaaaaa", crans: [21, 22, 23, 24] }
|
||||
];
|
||||
function segmentForCran(cran) {
|
||||
return WHEEL_SEGMENTS.find((s) => s.crans.includes(cran)) ?? WHEEL_SEGMENTS[0];
|
||||
}
|
||||
var CDEWheelApp = class _CDEWheelApp extends foundry.applications.api.ApplicationV2 {
|
||||
static DEFAULT_OPTIONS = {
|
||||
id: "cde-wheel-app",
|
||||
classes: ["cde-wheel-app"],
|
||||
tag: "div",
|
||||
window: {
|
||||
title: "CDE.InitiativeWheel",
|
||||
icon: "fas fa-circle-notch",
|
||||
resizable: true
|
||||
},
|
||||
position: { width: 820, height: 620 },
|
||||
actions: {
|
||||
advanceCran: _CDEWheelApp.#advanceCran,
|
||||
setSurprised: _CDEWheelApp.#setSurprised,
|
||||
rollInitiative: _CDEWheelApp.#rollInitiative
|
||||
}
|
||||
};
|
||||
/** @type {CDEWheelApp|null} */
|
||||
static #instance = null;
|
||||
/** Open (or bring to front) the singleton instance. */
|
||||
static open() {
|
||||
if (!_CDEWheelApp.#instance || _CDEWheelApp.#instance.rendered === false) {
|
||||
_CDEWheelApp.#instance = new _CDEWheelApp();
|
||||
_CDEWheelApp.#instance.render(true);
|
||||
} else {
|
||||
_CDEWheelApp.#instance.bringToFront();
|
||||
}
|
||||
return _CDEWheelApp.#instance;
|
||||
}
|
||||
/** Currently selected combatant id (for action panel). */
|
||||
#selectedId = null;
|
||||
async _prepareContext(options) {
|
||||
const combat = game.combat;
|
||||
const combatants = combat ? [...combat.combatants.values()] : [];
|
||||
const sorted = [...combatants].sort((a, b) => (b.initiative ?? 0) - (a.initiative ?? 0));
|
||||
const cranData = this.#buildCranData(combatants);
|
||||
const selected = this.#selectedId ? combatants.find((c) => c.id === this.#selectedId) : null;
|
||||
const actionCosts = ACTION_COSTS.map((a) => ({
|
||||
...a,
|
||||
label: game.i18n.localize(a.label)
|
||||
}));
|
||||
return {
|
||||
hasCombat: !!combat,
|
||||
combatants: sorted.map((c) => ({
|
||||
id: c.id,
|
||||
name: c.name,
|
||||
img: c.token?.texture?.src ?? c.actor?.img ?? "icons/svg/mystery-man.svg",
|
||||
initiative: c.initiative ?? "\u2014",
|
||||
segment: segmentForCran(c.initiative ?? 1),
|
||||
isActive: combat?.current?.combatantId === c.id,
|
||||
isSelected: c.id === this.#selectedId,
|
||||
hasInitiative: c.initiative != null
|
||||
})),
|
||||
cranData,
|
||||
selected,
|
||||
selectedName: selected?.name ?? null,
|
||||
actionCosts
|
||||
};
|
||||
}
|
||||
async _renderHTML(context, options) {
|
||||
return foundry.applications.handlebars.renderTemplate(WHEEL_TEMPLATE, context);
|
||||
}
|
||||
_replaceHTML(result, content, options) {
|
||||
content.innerHTML = result;
|
||||
this.#bindEvents(content);
|
||||
}
|
||||
/** Build per-cran data for the SVG wheel. */
|
||||
#buildCranData(combatants) {
|
||||
const data = [];
|
||||
for (let cran = 1; cran <= 24; cran++) {
|
||||
const segment = segmentForCran(cran);
|
||||
const fighters = combatants.filter((c) => Math.round(c.initiative) === cran);
|
||||
data.push({ cran, segment, fighters });
|
||||
}
|
||||
return data;
|
||||
}
|
||||
/** Bind click events for combatant selection. */
|
||||
#bindEvents(content) {
|
||||
content.querySelectorAll("[data-select-combatant]").forEach((el) => {
|
||||
el.addEventListener("click", () => {
|
||||
this.#selectedId = el.dataset.selectCombatant;
|
||||
this.render();
|
||||
});
|
||||
});
|
||||
}
|
||||
/** Action: advance selected combatant by given cran cost. */
|
||||
static async #advanceCran(event, element) {
|
||||
const app = _CDEWheelApp.#instance;
|
||||
if (!app?.#selectedId) return;
|
||||
const cost = parseInt(element.dataset.cost, 10);
|
||||
if (!cost || isNaN(cost)) return;
|
||||
const combatant = game.combat?.combatants.get(app.#selectedId);
|
||||
if (!combatant) return;
|
||||
await advanceCombatantPosition(combatant, cost);
|
||||
}
|
||||
/** Action: set selected combatant to surprised (position 1 = reference). */
|
||||
static async #setSurprised(event, element) {
|
||||
const app = _CDEWheelApp.#instance;
|
||||
if (!app?.#selectedId) return;
|
||||
const combatant = game.combat?.combatants.get(app.#selectedId);
|
||||
if (!combatant) return;
|
||||
await combatant.update({ initiative: 1 });
|
||||
}
|
||||
/** Action: open the initiative dialog for the selected combatant. */
|
||||
static async #rollInitiative(event, element) {
|
||||
const app = _CDEWheelApp.#instance;
|
||||
if (!app?.#selectedId) return;
|
||||
const combatant = game.combat?.combatants.get(app.#selectedId);
|
||||
if (!combatant) return;
|
||||
await game.combat.rollInitiative([app.#selectedId]);
|
||||
}
|
||||
/** Re-render when combat state changes. */
|
||||
static registerHooks() {
|
||||
const refresh = () => {
|
||||
if (_CDEWheelApp.#instance?.rendered) _CDEWheelApp.#instance.render();
|
||||
};
|
||||
Hooks.on("updateCombat", refresh);
|
||||
Hooks.on("updateCombatant", refresh);
|
||||
Hooks.on("createCombatant", refresh);
|
||||
Hooks.on("deleteCombatant", refresh);
|
||||
Hooks.on("updateActor", (_actor, diff) => {
|
||||
if (foundry.utils.hasProperty(diff, "system.initiative")) refresh();
|
||||
});
|
||||
Hooks.on("deleteCombat", () => {
|
||||
if (_CDEWheelApp.#instance?.rendered) _CDEWheelApp.#instance.render();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// src/ui/roll-actions.js
|
||||
var RESULT_TEMPLATE3 = "systems/fvtt-chroniques-de-l-etrange/templates/form/cde-dice-result.html";
|
||||
function injectRollActions(message, html) {
|
||||
@@ -2320,13 +3045,55 @@ function refreshAllRollActions() {
|
||||
});
|
||||
}
|
||||
|
||||
// src/ui/apps/welcome.js
|
||||
var HELP_JOURNAL_UUID = `Compendium.${SYSTEM_ID}.cde-help.JournalEntry.CDEGuideMain0001`;
|
||||
async function showWelcomeMessage() {
|
||||
const logo = `systems/${SYSTEM_ID}/images/logo_jeu.webp`;
|
||||
const content = `
|
||||
<div class="cde-welcome-message">
|
||||
<img class="cde-welcome-logo" src="${logo}" alt="Les Chroniques de l'\xC9trange" />
|
||||
<h2 class="cde-welcome-title">Les Chroniques de l'\xC9trange</h2>
|
||||
<p class="cde-welcome-links">
|
||||
Un jeu de r\xF4le \xE9dit\xE9 par
|
||||
<a href="https://antre-monde.com/les-chroniques-de-letrengae/" target="_blank" rel="noopener">Antre-Monde \xC9ditions</a>
|
||||
</p>
|
||||
<p class="cde-welcome-links">
|
||||
Syst\xE8me FoundryVTT r\xE9alis\xE9 par
|
||||
<a href="https://www.uberwald.me" target="_blank" rel="noopener">LeRatierBretonnien</a>
|
||||
</p>
|
||||
<button type="button" class="cde-welcome-help-btn" data-action="open-cde-help">
|
||||
<i class="fas fa-book-open"></i>
|
||||
${game.i18n.localize("CDE.WelcomeOpenHelp")}
|
||||
</button>
|
||||
</div>`;
|
||||
await ChatMessage.create({
|
||||
content,
|
||||
speaker: { alias: "Les Chroniques de l'\xC9trange" },
|
||||
flags: { [SYSTEM_ID]: { welcome: true } }
|
||||
});
|
||||
}
|
||||
function injectWelcomeActions(_message, html) {
|
||||
const el = html instanceof HTMLElement ? html : html[0] ?? html;
|
||||
const btn = el?.querySelector?.("[data-action='open-cde-help']");
|
||||
if (!btn) return;
|
||||
btn.addEventListener("click", async () => {
|
||||
try {
|
||||
const doc = await fromUuid(HELP_JOURNAL_UUID);
|
||||
doc?.sheet?.render(true);
|
||||
} catch {
|
||||
game.packs.get(`${SYSTEM_ID}.cde-help`)?.render(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// src/system.js
|
||||
Hooks.once("i18nInit", preLocalizeConfig);
|
||||
Hooks.once("init", async () => {
|
||||
console.info(`CHRONIQUESDELETRANGE | Initializing ${SYSTEM_ID}`);
|
||||
registerSettings();
|
||||
game.system.CONST = { MAGICS, SUBTYPES };
|
||||
game.cde = { CDELoksyuApp, CDETinjiApp };
|
||||
game.cde = { CDELoksyuApp, CDETinjiApp, CDEWheelApp };
|
||||
CONFIG.Combat.documentClass = CDECombat;
|
||||
CONFIG.Actor.dataModels = {
|
||||
[ACTOR_TYPES.character]: CharacterDataModel,
|
||||
[ACTOR_TYPES.npc]: NpcDataModel
|
||||
@@ -2404,6 +3171,9 @@ Hooks.once("init", async () => {
|
||||
});
|
||||
Hooks.once("ready", async () => {
|
||||
await migrateIfNeeded();
|
||||
await loadWelcomeSceneIfNeeded();
|
||||
CDEWheelApp.registerHooks();
|
||||
if (game.user.isGM) showWelcomeMessage();
|
||||
});
|
||||
Hooks.on("renderChatLog", (_app, html) => {
|
||||
const el = html instanceof HTMLElement ? html : html[0] ?? html;
|
||||
@@ -2418,10 +3188,14 @@ Hooks.on("renderChatLog", (_app, html) => {
|
||||
<button type="button" class="cde-chat-btn cde-chat-btn--tinji">
|
||||
<i class="fas fa-star"></i> ${game.i18n.localize("CDE.TinJi2")}
|
||||
</button>
|
||||
<button type="button" class="cde-chat-btn cde-chat-btn--wheel">
|
||||
<i class="fas fa-circle-notch"></i> ${game.i18n.localize("CDE.InitiativeWheel")}
|
||||
</button>
|
||||
`;
|
||||
wrapper.addEventListener("click", (ev) => {
|
||||
if (ev.target.closest(".cde-chat-btn--loksyu")) CDELoksyuApp.open();
|
||||
if (ev.target.closest(".cde-chat-btn--tinji")) CDETinjiApp.open();
|
||||
if (ev.target.closest(".cde-chat-btn--wheel")) CDEWheelApp.open();
|
||||
});
|
||||
const anchor = el.querySelector(".chat-form") ?? el.querySelector(".chat-message-form") ?? el.querySelector("form");
|
||||
if (anchor) anchor.parentElement.insertBefore(wrapper, anchor);
|
||||
@@ -2429,6 +3203,7 @@ Hooks.on("renderChatLog", (_app, html) => {
|
||||
});
|
||||
Hooks.on("renderChatMessageHTML", (message, html) => {
|
||||
injectRollActions(message, html);
|
||||
if (message.flags?.[SYSTEM_ID]?.welcome) injectWelcomeActions(message, html);
|
||||
});
|
||||
Hooks.on("updateSetting", (setting) => {
|
||||
if (!setting.key) return;
|
||||
@@ -2436,12 +3211,35 @@ Hooks.on("updateSetting", (setting) => {
|
||||
refreshAllRollActions();
|
||||
}
|
||||
});
|
||||
Hooks.on("updateActor", (actor, diff) => {
|
||||
if (!foundry.utils.hasProperty(diff, "system.initiative")) return;
|
||||
if (!game.combat) return;
|
||||
const initiative = actor.system.initiative;
|
||||
const combatant = game.combat.combatants.find((c) => c.actor?.id === actor.id);
|
||||
if (combatant && combatant.initiative !== initiative) {
|
||||
combatant.update({ initiative }).catch(() => {
|
||||
});
|
||||
}
|
||||
});
|
||||
Hooks.on("updateCombatant", (combatant, diff) => {
|
||||
if (!("initiative" in diff)) return;
|
||||
const initiative = combatant.initiative;
|
||||
if (initiative == null) return;
|
||||
setTimeout(() => {
|
||||
const actor = combatant.actor;
|
||||
if (actor && actor.system?.initiative !== initiative) {
|
||||
actor.update({ "system.initiative": initiative }).catch(() => {
|
||||
});
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
/**
|
||||
* Chroniques de l'Étrange — Système FoundryVTT
|
||||
*
|
||||
* Chroniques de l'Étrange est un jeu de rôle édité par Antre-Monde Éditions.
|
||||
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
|
||||
* affilié à Antre-Monde Éditions.
|
||||
* affilié à Antre-Monde Éditions,
|
||||
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
|
||||
*
|
||||
* @author LeRatierBretonnien
|
||||
* @copyright 2024–2026 LeRatierBretonnien
|
||||
|
||||
|
Before Width: | Height: | Size: 202 KiB |
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 134 B |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 64 KiB |
@@ -0,0 +1,58 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||
<defs>
|
||||
<radialGradient id="ar-glow" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" stop-color="#4ecdc4" stop-opacity="0.3"/>
|
||||
<stop offset="100%" stop-color="#4ecdc4" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Fond -->
|
||||
<circle cx="50" cy="50" r="48" fill="#101622" stroke="#1a2436" stroke-width="2"/>
|
||||
<circle cx="50" cy="50" r="48" fill="url(#ar-glow)"/>
|
||||
|
||||
<!-- Bouclier circulaire extérieur -->
|
||||
<circle cx="50" cy="50" r="36" fill="#4ecdc4" fill-opacity="0.08" stroke="#4ecdc4" stroke-width="2.5"/>
|
||||
<!-- Anneau décoratif intérieur -->
|
||||
<circle cx="50" cy="50" r="30" fill="none" stroke="#4ecdc4" stroke-width="1" opacity="0.5"/>
|
||||
|
||||
<!-- 8 rivets décoratifs sur le bord extérieur du bouclier -->
|
||||
<circle cx="50" cy="15" r="2" fill="#4ecdc4" opacity="0.8"/>
|
||||
<circle cx="75" cy="25" r="2" fill="#4ecdc4" opacity="0.8"/>
|
||||
<circle cx="85" cy="50" r="2" fill="#4ecdc4" opacity="0.8"/>
|
||||
<circle cx="75" cy="75" r="2" fill="#4ecdc4" opacity="0.8"/>
|
||||
<circle cx="50" cy="85" r="2" fill="#4ecdc4" opacity="0.8"/>
|
||||
<circle cx="25" cy="75" r="2" fill="#4ecdc4" opacity="0.8"/>
|
||||
<circle cx="15" cy="50" r="2" fill="#4ecdc4" opacity="0.8"/>
|
||||
<circle cx="25" cy="25" r="2" fill="#4ecdc4" opacity="0.8"/>
|
||||
|
||||
<!-- Tête de tigre stylisée -->
|
||||
<!-- Front / dessus de la tête -->
|
||||
<path d="M34 40 Q50 30 66 40 Q68 48 64 54 Q58 62 50 64 Q42 62 36 54 Q32 48 34 40 Z"
|
||||
fill="#4ecdc4" fill-opacity="0.15" stroke="#4ecdc4" stroke-width="2"/>
|
||||
<!-- Oreilles de tigre -->
|
||||
<path d="M34 40 Q28 30 32 24 Q36 32 40 38" fill="#4ecdc4" fill-opacity="0.2" stroke="#4ecdc4" stroke-width="1.5"/>
|
||||
<path d="M66 40 Q72 30 68 24 Q64 32 60 38" fill="#4ecdc4" fill-opacity="0.2" stroke="#4ecdc4" stroke-width="1.5"/>
|
||||
|
||||
<!-- Yeux du tigre -->
|
||||
<ellipse cx="43" cy="46" rx="5" ry="4" fill="none" stroke="#4ecdc4" stroke-width="1.5"/>
|
||||
<ellipse cx="57" cy="46" rx="5" ry="4" fill="none" stroke="#4ecdc4" stroke-width="1.5"/>
|
||||
<ellipse cx="43" cy="46" rx="2" ry="3" fill="#4ecdc4" fill-opacity="0.5"/>
|
||||
<ellipse cx="57" cy="46" rx="2" ry="3" fill="#4ecdc4" fill-opacity="0.5"/>
|
||||
|
||||
<!-- Nez du tigre -->
|
||||
<path d="M47 52 Q50 50 53 52 Q50 55 47 52 Z" fill="#4ecdc4" fill-opacity="0.6" stroke="#4ecdc4" stroke-width="1"/>
|
||||
<!-- Moustaches -->
|
||||
<line x1="32" y1="53" x2="45" y2="53" stroke="#4ecdc4" stroke-width="1" opacity="0.7"/>
|
||||
<line x1="55" y1="53" x2="68" y2="53" stroke="#4ecdc4" stroke-width="1" opacity="0.7"/>
|
||||
<line x1="33" y1="57" x2="45" y2="55" stroke="#4ecdc4" stroke-width="1" opacity="0.7"/>
|
||||
<line x1="55" y1="55" x2="67" y2="57" stroke="#4ecdc4" stroke-width="1" opacity="0.7"/>
|
||||
|
||||
<!-- Rayures de tigre sur le front -->
|
||||
<path d="M46 36 Q48 40 47 44" fill="none" stroke="#4ecdc4" stroke-width="1.5" stroke-linecap="round" opacity="0.6"/>
|
||||
<path d="M50 34 Q50 38 50 42" fill="none" stroke="#4ecdc4" stroke-width="1.5" stroke-linecap="round" opacity="0.6"/>
|
||||
<path d="M54 36 Q52 40 53 44" fill="none" stroke="#4ecdc4" stroke-width="1.5" stroke-linecap="round" opacity="0.6"/>
|
||||
|
||||
<!-- Crocs en bas -->
|
||||
<path d="M46 60 L44 68 L46 64 L48 70 L50 63 L52 70 L54 64 L56 68 L54 60"
|
||||
fill="none" stroke="#4ecdc4" stroke-width="1.5" stroke-linejoin="round" opacity="0.8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
@@ -0,0 +1,53 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||
<defs>
|
||||
<radialGradient id="ig-glow" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" stop-color="#98c379" stop-opacity="0.3"/>
|
||||
<stop offset="100%" stop-color="#98c379" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Fond -->
|
||||
<circle cx="50" cy="50" r="48" fill="#101622" stroke="#1a2436" stroke-width="2"/>
|
||||
<circle cx="50" cy="50" r="48" fill="url(#ig-glow)"/>
|
||||
|
||||
<!-- Pilon (pestle) — en haut à droite -->
|
||||
<line x1="62" y1="20" x2="52" y2="40" stroke="#98c379" stroke-width="3.5" stroke-linecap="round"/>
|
||||
<!-- Tête du pilon (plus épaisse) -->
|
||||
<ellipse cx="63" cy="19" rx="4" ry="3" fill="#98c379" fill-opacity="0.4" stroke="#98c379" stroke-width="1.5" transform="rotate(-30 63 19)"/>
|
||||
|
||||
<!-- Mortier (bol) -->
|
||||
<!-- Corps extérieur du mortier -->
|
||||
<path d="M22 54 Q22 76 50 80 Q78 76 78 54 Z"
|
||||
fill="#98c379" fill-opacity="0.12" stroke="#98c379" stroke-width="2.5"/>
|
||||
<!-- Bord supérieur du mortier (ellipse) -->
|
||||
<ellipse cx="50" cy="54" rx="28" ry="8" fill="#98c379" fill-opacity="0.1" stroke="#98c379" stroke-width="2"/>
|
||||
<!-- Contenu / intérieur sombre -->
|
||||
<ellipse cx="50" cy="56" rx="22" ry="6" fill="#101622" fill-opacity="0.6"/>
|
||||
<!-- Surface du contenu (herbes) -->
|
||||
<ellipse cx="50" cy="55" rx="18" ry="4" fill="#98c379" fill-opacity="0.2" stroke="#98c379" stroke-width="0.8" opacity="0.6"/>
|
||||
|
||||
<!-- Ornements du mortier (gravures) -->
|
||||
<!-- Motif vague (eau) sur le corps du mortier -->
|
||||
<path d="M26 64 Q32 60 38 64 Q44 68 50 64 Q56 60 62 64 Q68 68 74 64"
|
||||
fill="none" stroke="#98c379" stroke-width="1" opacity="0.5"/>
|
||||
|
||||
<!-- Fleur de lotus au-dessus du mortier -->
|
||||
<!-- Pétales (6) -->
|
||||
<path d="M50 44 Q46 36 42 30 Q46 34 50 32 Q54 34 58 30 Q54 36 50 44"
|
||||
fill="#98c379" fill-opacity="0.2" stroke="#98c379" stroke-width="1.5"/>
|
||||
<path d="M38 48 Q30 44 26 38 Q32 40 36 36 Q40 32 40 38 Q40 44 38 48"
|
||||
fill="#98c379" fill-opacity="0.2" stroke="#98c379" stroke-width="1.5"/>
|
||||
<path d="M62 48 Q70 44 74 38 Q68 40 64 36 Q60 32 60 38 Q60 44 62 48"
|
||||
fill="#98c379" fill-opacity="0.2" stroke="#98c379" stroke-width="1.5"/>
|
||||
<!-- Coeur du lotus -->
|
||||
<circle cx="50" cy="46" r="4" fill="#98c379" fill-opacity="0.4" stroke="#98c379" stroke-width="1.5"/>
|
||||
<circle cx="50" cy="46" r="1.5" fill="#98c379"/>
|
||||
|
||||
<!-- Vapeurs alchimiques -->
|
||||
<path d="M36 52 Q32 46 36 40 Q34 44 38 46 Q36 48 38 52"
|
||||
fill="none" stroke="#98c379" stroke-width="1" stroke-linecap="round" opacity="0.5"/>
|
||||
<path d="M50 52 Q48 44 52 38 Q50 44 54 46 Q52 48 52 52"
|
||||
fill="none" stroke="#98c379" stroke-width="1" stroke-linecap="round" opacity="0.5"/>
|
||||
<path d="M64 52 Q68 46 64 40 Q66 44 62 46 Q64 48 62 52"
|
||||
fill="none" stroke="#98c379" stroke-width="1" stroke-linecap="round" opacity="0.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
@@ -0,0 +1,59 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||
<defs>
|
||||
<radialGradient id="it-glow" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" stop-color="#00d4d4" stop-opacity="0.3"/>
|
||||
<stop offset="100%" stop-color="#00d4d4" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Fond -->
|
||||
<circle cx="50" cy="50" r="48" fill="#101622" stroke="#1a2436" stroke-width="2"/>
|
||||
<circle cx="50" cy="50" r="48" fill="url(#it-glow)"/>
|
||||
|
||||
<!-- Coffret laqué (corps) -->
|
||||
<!-- Couvercle -->
|
||||
<rect x="20" y="26" width="60" height="20" rx="3" fill="#00d4d4" fill-opacity="0.12" stroke="#00d4d4" stroke-width="2.5"/>
|
||||
<!-- Corps du coffret -->
|
||||
<rect x="18" y="46" width="64" height="32" rx="3" fill="#00d4d4" fill-opacity="0.08" stroke="#00d4d4" stroke-width="2.5"/>
|
||||
|
||||
<!-- Ligne de séparation couvercle/corps -->
|
||||
<line x1="18" y1="46" x2="82" y2="46" stroke="#00d4d4" stroke-width="1.5" opacity="0.7"/>
|
||||
|
||||
<!-- Ornements du couvercle -->
|
||||
<!-- Motif de losanges (style laque chinoise) -->
|
||||
<path d="M36 36 L50 28 L64 36 L50 44 Z" fill="none" stroke="#00d4d4" stroke-width="1" opacity="0.5"/>
|
||||
<!-- Lignes décoratives côtés couvercle -->
|
||||
<line x1="22" y1="30" x2="22" y2="42" stroke="#00d4d4" stroke-width="1" opacity="0.4"/>
|
||||
<line x1="78" y1="30" x2="78" y2="42" stroke="#00d4d4" stroke-width="1" opacity="0.4"/>
|
||||
|
||||
<!-- Serrure yin-yang au centre du corps du coffret -->
|
||||
<!-- Cercle de la serrure -->
|
||||
<circle cx="50" cy="60" r="9" fill="#101622" stroke="#00d4d4" stroke-width="2"/>
|
||||
<!-- Yin-yang simplifié dans la serrure -->
|
||||
<!-- Moitié yang (gauche, claire) -->
|
||||
<path d="M50 51 A9 9 0 0 0 50 69 A4.5 4.5 0 0 0 50 60 A4.5 4.5 0 0 1 50 51"
|
||||
fill="#00d4d4" fill-opacity="0.4"/>
|
||||
<!-- Petits cercles du yin-yang -->
|
||||
<circle cx="50" cy="55.5" r="2" fill="#101622"/>
|
||||
<circle cx="50" cy="64.5" r="2" fill="#00d4d4" fill-opacity="0.6"/>
|
||||
|
||||
<!-- Anneau de la serrure (trou de clé) -->
|
||||
<rect x="48" y="66" width="4" height="5" rx="1" fill="#00d4d4" fill-opacity="0.5"/>
|
||||
|
||||
<!-- Ferrures du coffret (coins) -->
|
||||
<!-- Coins du corps -->
|
||||
<path d="M18 46 L18 54 L24 54" fill="none" stroke="#00d4d4" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M82 46 L82 54 L76 54" fill="none" stroke="#00d4d4" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M18 78 L18 70 L24 70" fill="none" stroke="#00d4d4" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M82 78 L82 70 L76 70" fill="none" stroke="#00d4d4" stroke-width="1.5" stroke-linecap="round"/>
|
||||
|
||||
<!-- Charnières du coffret (sur le côté droit) -->
|
||||
<rect x="76" y="40" width="8" height="5" rx="2" fill="#00d4d4" fill-opacity="0.3" stroke="#00d4d4" stroke-width="1"/>
|
||||
<rect x="76" y="50" width="8" height="5" rx="2" fill="#00d4d4" fill-opacity="0.3" stroke="#00d4d4" stroke-width="1"/>
|
||||
|
||||
<!-- Motif décoratif sur le bas du coffret -->
|
||||
<path d="M26 72 Q50 68 74 72" fill="none" stroke="#00d4d4" stroke-width="1" stroke-dasharray="3 2" opacity="0.4"/>
|
||||
<!-- Petits ornements latéraux sur le corps -->
|
||||
<line x1="22" y1="56" x2="22" y2="68" stroke="#00d4d4" stroke-width="1" stroke-dasharray="2 2" opacity="0.4"/>
|
||||
<line x1="78" y1="56" x2="78" y2="68" stroke="#00d4d4" stroke-width="1" stroke-dasharray="2 2" opacity="0.4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
@@ -0,0 +1,38 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||
<!-- Fond circulaire -->
|
||||
<circle cx="50" cy="50" r="48" fill="#101622" stroke="#1a2436" stroke-width="2"/>
|
||||
<circle cx="50" cy="50" r="48" fill="url(#kg-glow)" fill-opacity="0.12"/>
|
||||
<defs>
|
||||
<radialGradient id="kg-glow" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" stop-color="#ff3d5a" stop-opacity="0.4"/>
|
||||
<stop offset="100%" stop-color="#ff3d5a" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Poing stylisé (vue de face, doigts repliés) -->
|
||||
<!-- Paume / base -->
|
||||
<rect x="32" y="52" width="36" height="20" rx="4" fill="#ff3d5a" fill-opacity="0.18" stroke="#ff3d5a" stroke-width="2"/>
|
||||
<!-- Doigts repliés — 4 rangées -->
|
||||
<rect x="33" y="38" width="8" height="16" rx="3" fill="#ff3d5a" fill-opacity="0.18" stroke="#ff3d5a" stroke-width="2"/>
|
||||
<rect x="43" y="35" width="8" height="19" rx="3" fill="#ff3d5a" fill-opacity="0.18" stroke="#ff3d5a" stroke-width="2"/>
|
||||
<rect x="53" y="36" width="8" height="18" rx="3" fill="#ff3d5a" fill-opacity="0.18" stroke="#ff3d5a" stroke-width="2"/>
|
||||
<rect x="63" y="40" width="6" height="14" rx="3" fill="#ff3d5a" fill-opacity="0.18" stroke="#ff3d5a" stroke-width="2"/>
|
||||
<!-- Pouce -->
|
||||
<path d="M32 62 Q24 60 25 54 Q26 50 32 52" fill="#ff3d5a" fill-opacity="0.18" stroke="#ff3d5a" stroke-width="2" stroke-linejoin="round"/>
|
||||
|
||||
<!-- Éclairs de qi (3 rayons) -->
|
||||
<!-- Éclair gauche-haut -->
|
||||
<polyline points="24,28 19,20 25,22 20,13" fill="none" stroke="#ff3d5a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" opacity="0.9"/>
|
||||
<!-- Éclair centre-haut -->
|
||||
<polyline points="50,30 47,20 52,23 49,13" fill="none" stroke="#ff3d5a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" opacity="0.9"/>
|
||||
<!-- Éclair droite-haut -->
|
||||
<polyline points="74,32 80,22 74,24 79,14" fill="none" stroke="#ff3d5a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" opacity="0.9"/>
|
||||
|
||||
<!-- Petites étincelles -->
|
||||
<circle cx="22" cy="35" r="1.5" fill="#ff3d5a" opacity="0.8"/>
|
||||
<circle cx="78" cy="37" r="1.5" fill="#ff3d5a" opacity="0.8"/>
|
||||
<circle cx="50" cy="26" r="1.5" fill="#ff3d5a" opacity="0.8"/>
|
||||
|
||||
<!-- Ligne de force sous le poing -->
|
||||
<path d="M28 73 Q50 80 72 73" fill="none" stroke="#ff3d5a" stroke-width="1.5" stroke-dasharray="3 2" opacity="0.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,43 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||
<defs>
|
||||
<radialGradient id="dm-glow" cx="50%" cy="45%" r="50%">
|
||||
<stop offset="0%" stop-color="#cc2222" stop-opacity="0.5"/>
|
||||
<stop offset="100%" stop-color="#cc2222" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Fond -->
|
||||
<circle cx="50" cy="50" r="48" fill="#101622" stroke="#1a2436" stroke-width="2"/>
|
||||
<circle cx="50" cy="50" r="48" fill="url(#dm-glow)"/>
|
||||
|
||||
<!-- Flammes infernales en arrière-plan -->
|
||||
<path d="M30 78 Q28 65 34 56 Q30 62 32 52 Q36 42 42 48 Q38 36 46 28 Q48 42 44 50 Q50 38 54 30 Q56 44 50 54 Q56 46 62 40 Q62 54 56 62 Q62 56 68 52 Q66 64 62 72 Q56 66 54 72 Q50 80 48 72 Q44 64 40 72 Q36 80 30 78 Z"
|
||||
fill="#cc2222" fill-opacity="0.18" stroke="#cc2222" stroke-width="1" stroke-linejoin="round" opacity="0.7"/>
|
||||
|
||||
<!-- Visage de démon - contour de tête -->
|
||||
<ellipse cx="50" cy="52" rx="20" ry="22" fill="#cc2222" fill-opacity="0.12" stroke="#cc2222" stroke-width="2"/>
|
||||
|
||||
<!-- Deux cornes -->
|
||||
<path d="M36 35 Q32 20 38 16 Q40 24 38 30" fill="#cc2222" fill-opacity="0.7" stroke="#cc2222" stroke-width="1.5" stroke-linejoin="round"/>
|
||||
<path d="M64 35 Q68 20 62 16 Q60 24 62 30" fill="#cc2222" fill-opacity="0.7" stroke="#cc2222" stroke-width="1.5" stroke-linejoin="round"/>
|
||||
|
||||
<!-- Yeux (forme de flamme) -->
|
||||
<path d="M40 50 Q44 44 48 50 Q44 56 40 50 Z" fill="#cc2222" opacity="0.9"/>
|
||||
<path d="M52 50 Q56 44 60 50 Q56 56 52 50 Z" fill="#cc2222" opacity="0.9"/>
|
||||
<!-- Pupilles -->
|
||||
<ellipse cx="44" cy="50" rx="1.5" ry="2.5" fill="#101622"/>
|
||||
<ellipse cx="56" cy="50" rx="1.5" ry="2.5" fill="#101622"/>
|
||||
|
||||
<!-- Nez aplati (démon) -->
|
||||
<path d="M47 58 Q50 60 53 58" fill="none" stroke="#cc2222" stroke-width="2" stroke-linecap="round" opacity="0.7"/>
|
||||
|
||||
<!-- Bouche avec crocs -->
|
||||
<path d="M38 66 Q50 74 62 66" fill="none" stroke="#cc2222" stroke-width="2" stroke-linecap="round" opacity="0.8"/>
|
||||
<!-- Crocs -->
|
||||
<path d="M43 67 L42 73 L45 68" fill="#cc2222" opacity="0.7"/>
|
||||
<path d="M57 67 L58 73 L55 68" fill="#cc2222" opacity="0.7"/>
|
||||
|
||||
<!-- Sourcils menaçants -->
|
||||
<path d="M38 44 Q44 40 48 43" fill="none" stroke="#cc2222" stroke-width="2.5" stroke-linecap="round" opacity="0.8"/>
|
||||
<path d="M52 43 Q56 40 62 44" fill="none" stroke="#cc2222" stroke-width="2.5" stroke-linecap="round" opacity="0.8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,44 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||
<defs>
|
||||
<radialGradient id="dv-glow" cx="50%" cy="40%" r="55%">
|
||||
<stop offset="0%" stop-color="#ddaa00" stop-opacity="0.5"/>
|
||||
<stop offset="100%" stop-color="#ddaa00" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Fond -->
|
||||
<circle cx="50" cy="50" r="48" fill="#101622" stroke="#1a2436" stroke-width="2"/>
|
||||
<circle cx="50" cy="50" r="48" fill="url(#dv-glow)"/>
|
||||
|
||||
<!-- Rayons célestes (halo) -->
|
||||
<line x1="50" y1="10" x2="50" y2="18" stroke="#ddaa00" stroke-width="2" opacity="0.6"/>
|
||||
<line x1="68" y1="15" x2="64" y2="22" stroke="#ddaa00" stroke-width="2" opacity="0.6"/>
|
||||
<line x1="80" y1="28" x2="74" y2="32" stroke="#ddaa00" stroke-width="2" opacity="0.6"/>
|
||||
<line x1="32" y1="15" x2="36" y2="22" stroke="#ddaa00" stroke-width="2" opacity="0.6"/>
|
||||
<line x1="20" y1="28" x2="26" y2="32" stroke="#ddaa00" stroke-width="2" opacity="0.6"/>
|
||||
<line x1="86" y1="44" x2="80" y2="46" stroke="#ddaa00" stroke-width="1.5" opacity="0.5"/>
|
||||
<line x1="14" y1="44" x2="20" y2="46" stroke="#ddaa00" stroke-width="1.5" opacity="0.5"/>
|
||||
|
||||
<!-- Halo circulaire doré -->
|
||||
<circle cx="50" cy="36" r="14" fill="none" stroke="#ddaa00" stroke-width="2.5" opacity="0.8"/>
|
||||
<circle cx="50" cy="36" r="16" fill="none" stroke="#ddaa00" stroke-width="0.8" opacity="0.4" stroke-dasharray="3,4"/>
|
||||
|
||||
<!-- Lotus (5 pétales) -->
|
||||
<ellipse cx="50" cy="72" rx="8" ry="4" fill="#ddaa00" fill-opacity="0.6" transform="rotate(0,50,72)"/>
|
||||
<ellipse cx="50" cy="72" rx="8" ry="4" fill="#ddaa00" fill-opacity="0.4" transform="rotate(36,50,72)"/>
|
||||
<ellipse cx="50" cy="72" rx="8" ry="4" fill="#ddaa00" fill-opacity="0.4" transform="rotate(72,50,72)"/>
|
||||
<ellipse cx="50" cy="72" rx="8" ry="4" fill="#ddaa00" fill-opacity="0.4" transform="rotate(108,50,72)"/>
|
||||
<ellipse cx="50" cy="72" rx="8" ry="4" fill="#ddaa00" fill-opacity="0.4" transform="rotate(144,50,72)"/>
|
||||
<!-- Cœur du lotus -->
|
||||
<circle cx="50" cy="72" r="4" fill="#ddaa00" fill-opacity="0.8"/>
|
||||
|
||||
<!-- Silhouette divine — corps lumineux -->
|
||||
<ellipse cx="50" cy="36" rx="8" ry="10" fill="#ddaa00" fill-opacity="0.25"/>
|
||||
<!-- Corps stylisé (robe longue) -->
|
||||
<path d="M44 44 Q40 58 42 72 Q50 68 58 72 Q60 58 56 44 Z"
|
||||
fill="#ddaa00" fill-opacity="0.18" stroke="#ddaa00" stroke-width="1.5"/>
|
||||
|
||||
<!-- Mains en prière -->
|
||||
<path d="M44 54 Q38 52 36 56 Q38 60 44 58" fill="#ddaa00" fill-opacity="0.3" stroke="#ddaa00" stroke-width="1.2"/>
|
||||
<path d="M56 54 Q62 52 64 56 Q62 60 56 58" fill="#ddaa00" fill-opacity="0.3" stroke="#ddaa00" stroke-width="1.2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1,41 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||
<defs>
|
||||
<radialGradient id="ea-glow" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" stop-color="#e8a030" stop-opacity="0.4"/>
|
||||
<stop offset="100%" stop-color="#e8a030" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Fond -->
|
||||
<circle cx="50" cy="50" r="48" fill="#101622" stroke="#1a2436" stroke-width="2"/>
|
||||
<circle cx="50" cy="50" r="48" fill="url(#ea-glow)"/>
|
||||
|
||||
<!-- Anneau extérieur -->
|
||||
<circle cx="50" cy="50" r="42" fill="none" stroke="#e8a030" stroke-width="1.2" opacity="0.4"/>
|
||||
|
||||
<!-- Yin-Yang stylisé (transformation) -->
|
||||
<!-- Demi-cercle yang (clair) -->
|
||||
<path d="M50 18 A32 32 0 0 1 50 82 A16 16 0 0 0 50 50 A16 16 0 0 1 50 18 Z"
|
||||
fill="#e8a030" fill-opacity="0.25" stroke="#e8a030" stroke-width="1.5"/>
|
||||
<!-- Demi-cercle yin (sombre) -->
|
||||
<path d="M50 18 A32 32 0 0 0 50 82 A16 16 0 0 1 50 50 A16 16 0 0 0 50 18 Z"
|
||||
fill="#e8a030" fill-opacity="0.05" stroke="#e8a030" stroke-width="1.5"/>
|
||||
<!-- Petits cercles yin-yang -->
|
||||
<circle cx="50" cy="34" r="6" fill="#e8a030" fill-opacity="0.6"/>
|
||||
<circle cx="50" cy="66" r="6" fill="#e8a030" fill-opacity="0.15" stroke="#e8a030" stroke-width="1.2"/>
|
||||
|
||||
<!-- Patte d'animal (5 coussinets) -->
|
||||
<!-- Coussinet principal (paume) -->
|
||||
<ellipse cx="50" cy="55" rx="9" ry="7" fill="#e8a030" fill-opacity="0.7"/>
|
||||
<!-- 4 orteils -->
|
||||
<ellipse cx="40" cy="44" rx="4" ry="3.5" fill="#e8a030" fill-opacity="0.7" transform="rotate(-15,40,44)"/>
|
||||
<ellipse cx="45" cy="41" rx="4" ry="3.5" fill="#e8a030" fill-opacity="0.7" transform="rotate(-5,45,41)"/>
|
||||
<ellipse cx="55" cy="41" rx="4" ry="3.5" fill="#e8a030" fill-opacity="0.7" transform="rotate(5,55,41)"/>
|
||||
<ellipse cx="60" cy="44" rx="4" ry="3.5" fill="#e8a030" fill-opacity="0.7" transform="rotate(15,60,44)"/>
|
||||
|
||||
<!-- Griffes stylisées -->
|
||||
<path d="M38 42 Q35 38 34 34" fill="none" stroke="#e8a030" stroke-width="1.8" stroke-linecap="round" opacity="0.8"/>
|
||||
<path d="M43 39 Q42 35 42 31" fill="none" stroke="#e8a030" stroke-width="1.8" stroke-linecap="round" opacity="0.8"/>
|
||||
<path d="M57 39 Q58 35 58 31" fill="none" stroke="#e8a030" stroke-width="1.8" stroke-linecap="round" opacity="0.8"/>
|
||||
<path d="M62 42 Q65 38 66 34" fill="none" stroke="#e8a030" stroke-width="1.8" stroke-linecap="round" opacity="0.8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,41 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||
<defs>
|
||||
<radialGradient id="gh-glow" cx="50%" cy="40%" r="55%">
|
||||
<stop offset="0%" stop-color="#88ccee" stop-opacity="0.45"/>
|
||||
<stop offset="100%" stop-color="#88ccee" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Fond -->
|
||||
<circle cx="50" cy="50" r="48" fill="#101622" stroke="#1a2436" stroke-width="2"/>
|
||||
<circle cx="50" cy="50" r="48" fill="url(#gh-glow)"/>
|
||||
|
||||
<!-- Silhouette fantôme — tête arrondie -->
|
||||
<path d="M34 44 Q34 24 50 22 Q66 24 66 44 L66 68 Q62 64 58 68 Q55 72 52 68 Q49 64 50 68 Q47 72 44 68 Q40 64 36 68 Q34 64 34 68 Z"
|
||||
fill="#88ccee" fill-opacity="0.15" stroke="#88ccee" stroke-width="2" stroke-linejoin="round"/>
|
||||
|
||||
<!-- Yeux fantôme — vides, inquiétants -->
|
||||
<ellipse cx="43" cy="42" rx="4" ry="5" fill="#101622" stroke="#88ccee" stroke-width="1.5" opacity="0.9"/>
|
||||
<ellipse cx="57" cy="42" rx="4" ry="5" fill="#101622" stroke="#88ccee" stroke-width="1.5" opacity="0.9"/>
|
||||
<!-- Lueurs dans les yeux -->
|
||||
<ellipse cx="43" cy="42" rx="1.5" ry="2" fill="#88ccee" opacity="0.5"/>
|
||||
<ellipse cx="57" cy="42" rx="1.5" ry="2" fill="#88ccee" opacity="0.5"/>
|
||||
|
||||
<!-- Kanji 鬼 stylisé en filigrane (simplifié) -->
|
||||
<!-- Traits horizontaux et verticaux évoquant le caractère -->
|
||||
<line x1="40" y1="53" x2="60" y2="53" stroke="#88ccee" stroke-width="1.5" opacity="0.5"/>
|
||||
<line x1="50" y1="53" x2="50" y2="62" stroke="#88ccee" stroke-width="1.5" opacity="0.5"/>
|
||||
<path d="M42 58 Q50 55 58 58" fill="none" stroke="#88ccee" stroke-width="1.5" opacity="0.5"/>
|
||||
|
||||
<!-- Traînée vaporeuse en bas -->
|
||||
<path d="M38 68 Q36 75 38 82 Q40 88 44 84 Q46 78 48 84 Q50 88 52 84 Q54 78 56 84 Q58 88 62 82 Q64 75 62 68"
|
||||
fill="#88ccee" fill-opacity="0.08" stroke="#88ccee" stroke-width="1.2" stroke-dasharray="3,3" opacity="0.6"/>
|
||||
|
||||
<!-- Chaînes (lien au monde des vivants) — deux petits maillons -->
|
||||
<circle cx="32" cy="52" r="3" fill="none" stroke="#88ccee" stroke-width="1.5" opacity="0.5"/>
|
||||
<circle cx="32" cy="58" r="3" fill="none" stroke="#88ccee" stroke-width="1.5" opacity="0.5"/>
|
||||
<line x1="32" y1="49" x2="32" y2="55" stroke="#88ccee" stroke-width="1.5" opacity="0.5"/>
|
||||
<circle cx="68" cy="52" r="3" fill="none" stroke="#88ccee" stroke-width="1.5" opacity="0.5"/>
|
||||
<circle cx="68" cy="58" r="3" fill="none" stroke="#88ccee" stroke-width="1.5" opacity="0.5"/>
|
||||
<line x1="68" y1="49" x2="68" y2="55" stroke="#88ccee" stroke-width="1.5" opacity="0.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,47 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||
<defs>
|
||||
<radialGradient id="jg-glow" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" stop-color="#22cc88" stop-opacity="0.4"/>
|
||||
<stop offset="100%" stop-color="#22cc88" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Fond -->
|
||||
<circle cx="50" cy="50" r="48" fill="#101622" stroke="#1a2436" stroke-width="2"/>
|
||||
<circle cx="50" cy="50" r="48" fill="url(#jg-glow)"/>
|
||||
|
||||
<!-- Cercle de transformation mystique -->
|
||||
<circle cx="50" cy="50" r="38" fill="none" stroke="#22cc88" stroke-width="1.2" opacity="0.35" stroke-dasharray="4,3"/>
|
||||
|
||||
<!-- Silhouette multi-forme (créature hybride) -->
|
||||
<!-- Corps central — humanoïde -->
|
||||
<ellipse cx="50" cy="52" rx="12" ry="15" fill="#22cc88" fill-opacity="0.15" stroke="#22cc88" stroke-width="1.8"/>
|
||||
|
||||
<!-- Tête avec cornes/oreilles animales -->
|
||||
<circle cx="50" cy="35" r="9" fill="#22cc88" fill-opacity="0.15" stroke="#22cc88" stroke-width="1.8"/>
|
||||
<!-- Oreilles pointues (animal) -->
|
||||
<path d="M41 30 L38 20 L45 28" fill="#22cc88" fill-opacity="0.5" stroke="#22cc88" stroke-width="1.5" stroke-linejoin="round"/>
|
||||
<path d="M59 30 L62 20 L55 28" fill="#22cc88" fill-opacity="0.5" stroke="#22cc88" stroke-width="1.5" stroke-linejoin="round"/>
|
||||
|
||||
<!-- Yeux (regard surnaturel) -->
|
||||
<ellipse cx="46" cy="35" rx="3" ry="2" fill="#22cc88" opacity="0.8"/>
|
||||
<ellipse cx="54" cy="35" rx="3" ry="2" fill="#22cc88" opacity="0.8"/>
|
||||
|
||||
<!-- Queue spiralée -->
|
||||
<path d="M62 60 Q72 58 74 65 Q76 72 68 74 Q62 74 62 68"
|
||||
fill="none" stroke="#22cc88" stroke-width="2" stroke-linecap="round" opacity="0.7"/>
|
||||
|
||||
<!-- Tentacule / membre supplémentaire gauche -->
|
||||
<path d="M38 55 Q26 52 22 60 Q20 68 28 66"
|
||||
fill="none" stroke="#22cc88" stroke-width="2" stroke-linecap="round" opacity="0.6"/>
|
||||
|
||||
<!-- Écailles / motif sur le corps -->
|
||||
<path d="M44 46 Q50 42 56 46 Q52 50 50 48 Q48 50 44 46 Z"
|
||||
fill="#22cc88" fill-opacity="0.3" stroke="#22cc88" stroke-width="1"/>
|
||||
<path d="M44 52 Q50 48 56 52 Q52 56 50 54 Q48 56 44 52 Z"
|
||||
fill="#22cc88" fill-opacity="0.3" stroke="#22cc88" stroke-width="1"/>
|
||||
|
||||
<!-- Kanji 怪 (étrange) stylisé — simplifié -->
|
||||
<text x="50" y="81" text-anchor="middle" font-size="11" font-family="serif"
|
||||
fill="#22cc88" opacity="0.55">怪</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,41 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||
<defs>
|
||||
<radialGradient id="mt-glow" cx="50%" cy="40%" r="50%">
|
||||
<stop offset="0%" stop-color="#6688aa" stop-opacity="0.35"/>
|
||||
<stop offset="100%" stop-color="#6688aa" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Fond -->
|
||||
<circle cx="50" cy="50" r="48" fill="#101622" stroke="#1a2436" stroke-width="2"/>
|
||||
<circle cx="50" cy="50" r="48" fill="url(#mt-glow)"/>
|
||||
|
||||
<!-- Anneau ésotérique (cercle de protection) -->
|
||||
<circle cx="50" cy="50" r="40" fill="none" stroke="#6688aa" stroke-width="1.2" opacity="0.4"/>
|
||||
<circle cx="50" cy="50" r="38" fill="none" stroke="#6688aa" stroke-width="0.6" opacity="0.25" stroke-dasharray="5,4"/>
|
||||
|
||||
<!-- Silhouette humaine -->
|
||||
<!-- Tête -->
|
||||
<circle cx="50" cy="30" r="9" fill="#6688aa" fill-opacity="0.25" stroke="#6688aa" stroke-width="1.8"/>
|
||||
<!-- Corps -->
|
||||
<path d="M40 42 L36 66 L42 66 L45 56 L50 60 L55 56 L58 66 L64 66 L60 42 Z"
|
||||
fill="#6688aa" fill-opacity="0.2" stroke="#6688aa" stroke-width="1.8" stroke-linejoin="round"/>
|
||||
<!-- Bras gauche -->
|
||||
<path d="M40 44 Q30 52 28 60" fill="none" stroke="#6688aa" stroke-width="2" stroke-linecap="round"/>
|
||||
<!-- Bras droit (levé, tenant un talisman) -->
|
||||
<path d="M60 44 Q70 48 72 42" fill="none" stroke="#6688aa" stroke-width="2" stroke-linecap="round"/>
|
||||
|
||||
<!-- Talisman / ofuda (papier de prière) -->
|
||||
<rect x="68" y="32" width="10" height="14" rx="1"
|
||||
fill="#6688aa" fill-opacity="0.2" stroke="#6688aa" stroke-width="1.5"/>
|
||||
<!-- Lignes du talisman -->
|
||||
<line x1="70" y1="36" x2="76" y2="36" stroke="#6688aa" stroke-width="1" opacity="0.7"/>
|
||||
<line x1="70" y1="39" x2="76" y2="39" stroke="#6688aa" stroke-width="1" opacity="0.7"/>
|
||||
<line x1="70" y1="42" x2="76" y2="42" stroke="#6688aa" stroke-width="1" opacity="0.7"/>
|
||||
|
||||
<!-- Symbole occulte sur la poitrine (trigramme Pa Kua simplifié) -->
|
||||
<line x1="45" y1="46" x2="55" y2="46" stroke="#6688aa" stroke-width="1.5" opacity="0.6"/>
|
||||
<line x1="45" y1="49" x2="55" y2="49" stroke="#6688aa" stroke-width="1.5" opacity="0.6"/>
|
||||
<line x1="45" y1="52" x2="50" y2="52" stroke="#6688aa" stroke-width="1.5" opacity="0.6"/>
|
||||
<line x1="52" y1="52" x2="55" y2="52" stroke="#6688aa" stroke-width="1.5" opacity="0.6"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,58 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||
<defs>
|
||||
<radialGradient id="sh-glow" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" stop-color="#cc44ff" stop-opacity="0.35"/>
|
||||
<stop offset="100%" stop-color="#cc44ff" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Fond -->
|
||||
<circle cx="50" cy="50" r="48" fill="#101622" stroke="#1a2436" stroke-width="2"/>
|
||||
<circle cx="50" cy="50" r="48" fill="url(#sh-glow)"/>
|
||||
|
||||
<!-- Disque Bi (玉璧) — anneau de jade -->
|
||||
<!-- Anneau extérieur -->
|
||||
<circle cx="50" cy="50" r="32" fill="#cc44ff" fill-opacity="0.12" stroke="#cc44ff" stroke-width="2.5"/>
|
||||
<!-- Trou central du bi -->
|
||||
<circle cx="50" cy="50" r="14" fill="#101622" stroke="#cc44ff" stroke-width="2"/>
|
||||
|
||||
<!-- Gravures décoratives sur le disque (surface du bi) -->
|
||||
<!-- Spirales de grain (yun wen) — motif traditionnel des bi -->
|
||||
<circle cx="50" cy="50" r="23" fill="none" stroke="#cc44ff" stroke-width="0.8" stroke-dasharray="2.5 2" opacity="0.5"/>
|
||||
<circle cx="50" cy="50" r="19" fill="none" stroke="#cc44ff" stroke-width="0.8" stroke-dasharray="1.5 3" opacity="0.4"/>
|
||||
|
||||
<!-- Petits motifs en spirale sur la surface du bi (8 grains) -->
|
||||
<path d="M50 22 Q52 20 54 22 Q52 24 50 22" fill="none" stroke="#cc44ff" stroke-width="1" opacity="0.7"/>
|
||||
<path d="M68 32 Q70 30 72 32 Q70 34 68 32" fill="none" stroke="#cc44ff" stroke-width="1" opacity="0.7"/>
|
||||
<path d="M78 50 Q80 48 82 50 Q80 52 78 50" fill="none" stroke="#cc44ff" stroke-width="1" opacity="0.7"/>
|
||||
<path d="M68 68 Q70 66 72 68 Q70 70 68 68" fill="none" stroke="#cc44ff" stroke-width="1" opacity="0.7"/>
|
||||
<path d="M50 78 Q52 76 54 78 Q52 80 50 78" fill="none" stroke="#cc44ff" stroke-width="1" opacity="0.7"/>
|
||||
<path d="M28 68 Q30 66 32 68 Q30 70 28 68" fill="none" stroke="#cc44ff" stroke-width="1" opacity="0.7"/>
|
||||
<path d="M18 50 Q20 48 22 50 Q20 52 18 50" fill="none" stroke="#cc44ff" stroke-width="1" opacity="0.7"/>
|
||||
<path d="M28 32 Q30 30 32 32 Q30 34 28 32" fill="none" stroke="#cc44ff" stroke-width="1" opacity="0.7"/>
|
||||
|
||||
<!-- 3 Perles (San = 3 en cantonais) flottant autour du bi -->
|
||||
<!-- Perle 1 — haut -->
|
||||
<circle cx="50" cy="10" r="5" fill="#cc44ff" fill-opacity="0.25" stroke="#cc44ff" stroke-width="1.8"/>
|
||||
<circle cx="48" cy="8" r="1.5" fill="#cc44ff" fill-opacity="0.6"/>
|
||||
<!-- Fil de perle haut -->
|
||||
<line x1="50" y1="15" x2="50" y2="18" stroke="#cc44ff" stroke-width="1" stroke-dasharray="2 1" opacity="0.6"/>
|
||||
|
||||
<!-- Perle 2 — bas-gauche -->
|
||||
<circle cx="26" cy="84" r="5" fill="#cc44ff" fill-opacity="0.25" stroke="#cc44ff" stroke-width="1.8"/>
|
||||
<circle cx="24" cy="82" r="1.5" fill="#cc44ff" fill-opacity="0.6"/>
|
||||
<!-- Fil perle bas-gauche -->
|
||||
<line x1="30" y1="80" x2="34" y2="76" stroke="#cc44ff" stroke-width="1" stroke-dasharray="2 1" opacity="0.6"/>
|
||||
|
||||
<!-- Perle 3 — bas-droit -->
|
||||
<circle cx="74" cy="84" r="5" fill="#cc44ff" fill-opacity="0.25" stroke="#cc44ff" stroke-width="1.8"/>
|
||||
<circle cx="72" cy="82" r="1.5" fill="#cc44ff" fill-opacity="0.6"/>
|
||||
<!-- Fil perle bas-droit -->
|
||||
<line x1="70" y1="80" x2="66" y2="76" stroke="#cc44ff" stroke-width="1" stroke-dasharray="2 1" opacity="0.6"/>
|
||||
|
||||
<!-- Runes dans le trou central -->
|
||||
<!-- Symbole 三 (3 traits) -->
|
||||
<line x1="44" y1="46" x2="56" y2="46" stroke="#cc44ff" stroke-width="1.5" opacity="0.8"/>
|
||||
<line x1="43" y1="50" x2="57" y2="50" stroke="#cc44ff" stroke-width="1.5" opacity="0.8"/>
|
||||
<line x1="44" y1="54" x2="56" y2="54" stroke="#cc44ff" stroke-width="1.5" opacity="0.8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
@@ -0,0 +1,62 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||
<defs>
|
||||
<radialGradient id="sp-glow" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" stop-color="#4a9eff" stop-opacity="0.35"/>
|
||||
<stop offset="100%" stop-color="#4a9eff" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Fond -->
|
||||
<circle cx="50" cy="50" r="48" fill="#101622" stroke="#1a2436" stroke-width="2"/>
|
||||
<circle cx="50" cy="50" r="48" fill="url(#sp-glow)"/>
|
||||
|
||||
<!-- Octogone extérieur (bagua) -->
|
||||
<polygon points="50,18 68,24 79,40 79,60 68,76 50,82 32,76 21,60 21,40 32,24"
|
||||
fill="none" stroke="#4a9eff" stroke-width="1.8" opacity="0.7"/>
|
||||
|
||||
<!-- 8 trigrammes sur les côtés (petites lignes stylisées) -->
|
||||
<!-- Chaque trigramme = 3 lignes, positionnées sur les 8 côtés de l'octogone -->
|
||||
<!-- Nord -->
|
||||
<line x1="46" y1="13" x2="54" y2="13" stroke="#4a9eff" stroke-width="1.5"/>
|
||||
<line x1="46" y1="16" x2="54" y2="16" stroke="#4a9eff" stroke-width="1.5"/>
|
||||
<line x1="46" y1="19" x2="54" y2="19" stroke="#4a9eff" stroke-width="1.5"/>
|
||||
<!-- Nord-Est -->
|
||||
<line x1="66" y1="18" x2="72" y2="22" stroke="#4a9eff" stroke-width="1.5"/>
|
||||
<line x1="64" y1="21" x2="70" y2="25" stroke="#4a9eff" stroke-width="1.5"/>
|
||||
<line x1="62" y1="24" x2="66" y2="27" stroke="#4a9eff" stroke-width="1.5"/>
|
||||
<!-- Est -->
|
||||
<line x1="81" y1="46" x2="87" y2="46" stroke="#4a9eff" stroke-width="1.5"/>
|
||||
<line x1="81" y1="50" x2="87" y2="50" stroke="#4a9eff" stroke-width="1.5"/>
|
||||
<line x1="83" y1="54" x2="87" y2="54" stroke="#4a9eff" stroke-width="1.5"/>
|
||||
<!-- Sud-Est -->
|
||||
<line x1="66" y1="78" x2="72" y2="74" stroke="#4a9eff" stroke-width="1.5"/>
|
||||
<line x1="64" y1="75" x2="70" y2="71" stroke="#4a9eff" stroke-width="1.5"/>
|
||||
<line x1="62" y1="72" x2="68" y2="69" stroke="#4a9eff" stroke-width="1.5"/>
|
||||
<!-- Sud -->
|
||||
<line x1="46" y1="87" x2="54" y2="87" stroke="#4a9eff" stroke-width="1.5"/>
|
||||
<line x1="46" y1="84" x2="54" y2="84" stroke="#4a9eff" stroke-width="1.5"/>
|
||||
<line x1="46" y1="81" x2="54" y2="81" stroke="#4a9eff" stroke-width="1.5"/>
|
||||
<!-- Sud-Ouest -->
|
||||
<line x1="34" y1="78" x2="28" y2="74" stroke="#4a9eff" stroke-width="1.5"/>
|
||||
<line x1="36" y1="75" x2="30" y2="71" stroke="#4a9eff" stroke-width="1.5"/>
|
||||
<line x1="38" y1="72" x2="32" y2="69" stroke="#4a9eff" stroke-width="1.5"/>
|
||||
<!-- Ouest -->
|
||||
<line x1="19" y1="46" x2="13" y2="46" stroke="#4a9eff" stroke-width="1.5"/>
|
||||
<line x1="19" y1="50" x2="13" y2="50" stroke="#4a9eff" stroke-width="1.5"/>
|
||||
<line x1="17" y1="54" x2="13" y2="54" stroke="#4a9eff" stroke-width="1.5"/>
|
||||
<!-- Nord-Ouest -->
|
||||
<line x1="34" y1="18" x2="28" y2="22" stroke="#4a9eff" stroke-width="1.5"/>
|
||||
<line x1="36" y1="21" x2="30" y2="25" stroke="#4a9eff" stroke-width="1.5"/>
|
||||
<line x1="38" y1="24" x2="34" y2="27" stroke="#4a9eff" stroke-width="1.5"/>
|
||||
|
||||
<!-- Cercle intérieur -->
|
||||
<circle cx="50" cy="50" r="22" fill="none" stroke="#4a9eff" stroke-width="1.5" opacity="0.6"/>
|
||||
|
||||
<!-- Spirale de cinnabre (qi) — double spirale -->
|
||||
<path d="M50 50 Q54 42 50 38 Q44 34 40 40 Q36 48 42 54 Q50 62 60 56 Q68 48 62 38 Q54 28 42 32"
|
||||
fill="none" stroke="#4a9eff" stroke-width="2" stroke-linecap="round" opacity="0.9"/>
|
||||
|
||||
<!-- Point central (cinnabre) -->
|
||||
<circle cx="50" cy="50" r="4" fill="#4a9eff" fill-opacity="0.5" stroke="#4a9eff" stroke-width="1.5"/>
|
||||
<circle cx="50" cy="50" r="1.5" fill="#4a9eff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
@@ -0,0 +1,49 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||
<defs>
|
||||
<radialGradient id="sn-glow" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" stop-color="#cc44ff" stop-opacity="0.4"/>
|
||||
<stop offset="100%" stop-color="#cc44ff" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Fond -->
|
||||
<circle cx="50" cy="50" r="48" fill="#101622" stroke="#1a2436" stroke-width="2"/>
|
||||
<circle cx="50" cy="50" r="48" fill="url(#sn-glow)"/>
|
||||
|
||||
<!-- Flammes surnaturelles (gauche) -->
|
||||
<path d="M22 72 Q20 60 26 52 Q22 56 24 46 Q28 38 32 42 Q28 32 36 26 Q38 38 34 44 Q40 36 42 28 Q46 40 40 50 Q46 44 48 36 Q52 48 46 56 Q50 50 54 44 Q56 56 50 64 Q54 58 58 52 Q60 62 56 70 Q52 68 50 72"
|
||||
fill="#cc44ff" fill-opacity="0.2" stroke="#cc44ff" stroke-width="1.5" stroke-linejoin="round" opacity="0.8"/>
|
||||
|
||||
<!-- Contour de l'œil (grand) -->
|
||||
<path d="M20 50 Q35 30 50 30 Q65 30 80 50 Q65 70 50 70 Q35 70 20 50 Z"
|
||||
fill="none" stroke="#cc44ff" stroke-width="2" opacity="0.9"/>
|
||||
<!-- Remplissage doux de l'œil -->
|
||||
<path d="M25 50 Q38 36 50 36 Q62 36 75 50 Q62 64 50 64 Q38 64 25 50 Z"
|
||||
fill="#cc44ff" fill-opacity="0.08"/>
|
||||
|
||||
<!-- Iris de l'œil -->
|
||||
<circle cx="50" cy="50" r="10" fill="none" stroke="#cc44ff" stroke-width="2" opacity="0.8"/>
|
||||
<!-- Pupille verticale (reptilienne / surnaturelle) -->
|
||||
<ellipse cx="50" cy="50" rx="3" ry="8" fill="#cc44ff" fill-opacity="0.6"/>
|
||||
<!-- Reflet -->
|
||||
<circle cx="47" cy="46" r="2" fill="#cc44ff" fill-opacity="0.5"/>
|
||||
|
||||
<!-- Flammes supérieures (au-dessus de l'œil) -->
|
||||
<path d="M38 30 Q36 22 42 18 Q40 26 46 22 Q44 28 50 24 Q48 30 54 26 Q52 32 58 28 Q56 34 62 30"
|
||||
fill="none" stroke="#cc44ff" stroke-width="1.8" stroke-linecap="round" opacity="0.9"/>
|
||||
|
||||
<!-- Lignes de rayonnement autour de l'œil -->
|
||||
<line x1="50" y1="24" x2="50" y2="18" stroke="#cc44ff" stroke-width="1.5" opacity="0.6"/>
|
||||
<line x1="50" y1="76" x2="50" y2="82" stroke="#cc44ff" stroke-width="1.5" opacity="0.6"/>
|
||||
<line x1="14" y1="50" x2="8" y2="50" stroke="#cc44ff" stroke-width="1.5" opacity="0.6"/>
|
||||
<line x1="86" y1="50" x2="92" y2="50" stroke="#cc44ff" stroke-width="1.5" opacity="0.6"/>
|
||||
<!-- Diagonaux -->
|
||||
<line x1="27" y1="27" x2="22" y2="22" stroke="#cc44ff" stroke-width="1.2" opacity="0.5"/>
|
||||
<line x1="73" y1="27" x2="78" y2="22" stroke="#cc44ff" stroke-width="1.2" opacity="0.5"/>
|
||||
<line x1="27" y1="73" x2="22" y2="78" stroke="#cc44ff" stroke-width="1.2" opacity="0.5"/>
|
||||
<line x1="73" y1="73" x2="78" y2="78" stroke="#cc44ff" stroke-width="1.2" opacity="0.5"/>
|
||||
|
||||
<!-- Spirales surnaturelles aux coins -->
|
||||
<path d="M16 22 Q20 16 26 20 Q22 24 18 22" fill="none" stroke="#cc44ff" stroke-width="1.2" opacity="0.6"/>
|
||||
<path d="M84 22 Q80 16 74 20 Q78 24 82 22" fill="none" stroke="#cc44ff" stroke-width="1.2" opacity="0.6"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
@@ -0,0 +1,46 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||
<defs>
|
||||
<radialGradient id="wp-glow" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" stop-color="#ff6b35" stop-opacity="0.3"/>
|
||||
<stop offset="100%" stop-color="#ff6b35" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Fond -->
|
||||
<circle cx="50" cy="50" r="48" fill="#101622" stroke="#1a2436" stroke-width="2"/>
|
||||
<circle cx="50" cy="50" r="48" fill="url(#wp-glow)"/>
|
||||
|
||||
<!-- Dao (sabre courbé) — de bas-gauche à haut-droit -->
|
||||
<!-- Lame du dao (légèrement courbée) -->
|
||||
<path d="M22 78 Q30 60 45 45 Q58 32 72 22"
|
||||
fill="none" stroke="#ff6b35" stroke-width="3.5" stroke-linecap="round"/>
|
||||
<!-- Dos de la lame (trait intérieur plus fin) -->
|
||||
<path d="M25 76 Q33 58 47 44 Q59 33 72 24"
|
||||
fill="none" stroke="#ff6b35" stroke-width="1" stroke-linecap="round" opacity="0.5"/>
|
||||
<!-- Brillance de la lame -->
|
||||
<path d="M28 73 Q38 56 52 42"
|
||||
fill="none" stroke="#ff6b35" stroke-width="0.8" stroke-linecap="round" opacity="0.8" stroke-dasharray="2 3"/>
|
||||
<!-- Garde du dao (petite croix ornementée) -->
|
||||
<path d="M36 66 Q38 60 46 56 Q50 54 56 58 Q50 62 44 68 Z"
|
||||
fill="#ff6b35" fill-opacity="0.3" stroke="#ff6b35" stroke-width="1.5"/>
|
||||
<!-- Poignée du dao -->
|
||||
<line x1="22" y1="78" x2="15" y2="86" stroke="#ff6b35" stroke-width="4" stroke-linecap="round" opacity="0.7"/>
|
||||
<rect x="13" y="84" width="6" height="4" rx="2" fill="#ff6b35" fill-opacity="0.5" stroke="#ff6b35" stroke-width="1" transform="rotate(-45 16 86)"/>
|
||||
|
||||
<!-- Lance — de bas-droit à haut-gauche -->
|
||||
<!-- Hampe de la lance -->
|
||||
<line x1="78" y1="78" x2="22" y2="22" stroke="#ff6b35" stroke-width="2" stroke-linecap="round" opacity="0.7"/>
|
||||
<!-- Pointe de la lance (haut-gauche) -->
|
||||
<path d="M22 22 L16 14 L20 20 L14 18 Z" fill="#ff6b35" stroke="#ff6b35" stroke-width="1" stroke-linejoin="round"/>
|
||||
<!-- Ornement milieu de hampe -->
|
||||
<path d="M44 44 Q48 40 52 44 Q48 48 44 44 Z" fill="#ff6b35" fill-opacity="0.4" stroke="#ff6b35" stroke-width="1"/>
|
||||
<!-- Embout de la lance (bas-droit) -->
|
||||
<path d="M78 78 L84 85 L80 80 L86 82 Z" fill="#ff6b35" stroke="#ff6b35" stroke-width="1" stroke-linejoin="round" opacity="0.7"/>
|
||||
|
||||
<!-- Petites étincelles au croisement -->
|
||||
<circle cx="50" cy="50" r="3" fill="#ff6b35" fill-opacity="0.3" stroke="#ff6b35" stroke-width="1.5"/>
|
||||
<line x1="50" y1="44" x2="50" y2="40" stroke="#ff6b35" stroke-width="1" opacity="0.7"/>
|
||||
<line x1="56" y1="50" x2="60" y2="50" stroke="#ff6b35" stroke-width="1" opacity="0.7"/>
|
||||
<line x1="44" y1="50" x2="40" y2="50" stroke="#ff6b35" stroke-width="1" opacity="0.7"/>
|
||||
<line x1="50" y1="56" x2="50" y2="60" stroke="#ff6b35" stroke-width="1" opacity="0.7"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 66 KiB |