history: rojo chibi bone animator prototype

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Rokojori 2026-07-06 22:05:27 +02:00
parent 1dc40fa22c
commit 7b0c01560c
5 changed files with 535 additions and 7 deletions

164
src/rojos/rojo-animator.ts Normal file
View File

@ -0,0 +1,164 @@
const InkscapeNS = "http://www.inkscape.org/namespaces/inkscape";
const N = "(-?[\\d.eE+]+)";
const S = "[,\\s]+";
const TranslateRE = new RegExp( `translate\\(\\s*${ N }${ S }${ N }\\s*\\)` );
const MatrixRE = new RegExp( `matrix\\(\\s*${ N }${ S }${ N }${ S }${ N }${ S }${ N }${ S }${ N }${ S }${ N }\\s*\\)` );
const RotateRE = new RegExp( `rotate\\(\\s*${ N }(?:${ S }${ N }${ S }${ N })?\\s*\\)` );
function applyTransformStr( s: string, x: number, y: number ): [ number, number ]
{
if ( null == s || "" === s ) { return [ x, y ]; }
let m = s.match( TranslateRE );
if ( null != m )
{
return [ x + parseFloat( m[ 1 ] ), y + parseFloat( m[ 2 ] ) ];
}
m = s.match( MatrixRE );
if ( null != m )
{
const a = parseFloat( m[ 1 ] ), b = parseFloat( m[ 2 ] );
const c = parseFloat( m[ 3 ] ), d = parseFloat( m[ 4 ] );
const e = parseFloat( m[ 5 ] ), f = parseFloat( m[ 6 ] );
return [ a * x + c * y + e, b * x + d * y + f ];
}
m = s.match( RotateRE );
if ( null != m )
{
const angle = parseFloat( m[ 1 ] ) * Math.PI / 180;
const cos = Math.cos( angle );
const sin = Math.sin( angle );
const cx = null != m[ 2 ] ? parseFloat( m[ 2 ] ) : 0;
const cy = null != m[ 3 ] ? parseFloat( m[ 3 ] ) : 0;
const dx = x - cx;
const dy = y - cy;
return [ cx + dx * cos - dy * sin, cy + dx * sin + dy * cos ];
}
return [ x, y ];
}
export class BoneData
{
group: SVGGElement = null;
name: string = "";
restTransform: string = "";
pivotLocalX: number = 0;
pivotLocalY: number = 0;
}
export class RojoAnimator
{
svgRoot: SVGSVGElement = null;
Bones: Map<string, BoneData> = new Map();
static create( svgRoot: SVGSVGElement )
{
let ra = new RojoAnimator();
ra.svgRoot = svgRoot;
ra._processBones();
ra._stripInkscape();
return ra;
}
_label( el: Element )
{
return el.getAttributeNS( InkscapeNS, "label" ) ?? "";
}
_processBones()
{
const groups = Array.from( this.svgRoot.querySelectorAll( "g" ) );
for ( const g of groups )
{
const label = this._label( g );
if ( !label.endsWith( "-transform" ) ) { continue; }
const name = label.slice( 0, label.length - "-transform".length );
const restTransform = g.getAttribute( "transform" ) ?? "";
const pivotLabel = name + "-pivot";
let pivotCircle: Element = null;
for ( const child of Array.from( g.children ) )
{
if ( "circle" === child.localName && pivotLabel === this._label( child ) )
{
pivotCircle = child;
break;
}
}
if ( null == pivotCircle ) { continue; }
const cx = parseFloat( pivotCircle.getAttribute( "cx" ) ?? "0" );
const cy = parseFloat( pivotCircle.getAttribute( "cy" ) ?? "0" );
const pivotTransform = pivotCircle.getAttribute( "transform" ) ?? "";
const [ lx, ly ] = applyTransformStr( pivotTransform, cx, cy );
pivotCircle.remove();
const bone = new BoneData();
bone.group = g as SVGGElement;
bone.name = name;
bone.restTransform = restTransform;
bone.pivotLocalX = lx;
bone.pivotLocalY = ly;
this.Bones.set( name, bone );
}
}
_stripInkscape()
{
const namedview = Array.from( this.svgRoot.children )
.find( el => "namedview" === el.localName );
if ( null != namedview ) { namedview.remove(); }
const all: Element[] = [ this.svgRoot, ...Array.from( this.svgRoot.querySelectorAll( "*" ) ) ];
for ( const el of all )
{
const toRemove: string[] = [];
for ( let i = 0; i < el.attributes.length; i++ )
{
const { name } = el.attributes[ i ];
if ( name.startsWith( "inkscape:" ) || name.startsWith( "sodipodi:" ) )
{
toRemove.push( name );
}
}
for ( const attr of toRemove )
{
el.removeAttribute( attr );
}
}
}
setBoneAngle( name: string, degrees: number )
{
const bone = this.Bones.get( name );
if ( null == bone ) { return; }
const rest = bone.restTransform;
const lx = bone.pivotLocalX;
const ly = bone.pivotLocalY;
const rotation = `translate(${ lx },${ ly }) rotate(${ degrees }) translate(${ -lx },${ -ly })`;
const transform = "" === rest ? rotation : `${ rest } ${ rotation }`;
bone.group.setAttribute( "transform", transform );
}
}

298
src/rojos/rojo-base.svg Normal file
View File

@ -0,0 +1,298 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="28.966585"
height="38.722172"
viewBox="0 0 28.966585 38.722172"
version="1.1"
id="svg5"
inkscape:version="1.4.3 (0d15f75, 2025-12-25)"
sodipodi:docname="rojo-base.svg"
xml:space="preserve"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview7"
pagecolor="#333333"
bordercolor="#404040"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#333333"
inkscape:document-units="px"
showgrid="false"
inkscape:zoom="9.4539622"
inkscape:cx="-14.755718"
inkscape:cy="24.910191"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg5" /><defs
id="defs2"><clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath7940"><rect
style="fill:#333333;fill-opacity:1;stroke:none;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="rect7942"
width="1440"
height="810"
x="0"
y="0" /></clipPath></defs><g
id="g26"
inkscape:label="root-transform"
transform="translate(-794.40483,-571.077)"><g
id="g12"
inkscape:label="body-transform"><g
id="g11"
inkscape:label="leg-r-transform"
transform="translate(0.78025742,1.094282)"><rect
style="opacity:1;fill:#e64242;fill-opacity:1;stroke:#390000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="rect4"
width="2.1739705"
height="5.6944928"
x="810.83917"
y="595.75208"
ry="0.55225909"
inkscape:label="leg-r" /><rect
style="opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="rect9"
width="2.1332026"
height="0.91032934"
x="810.77997"
y="601.66357"
ry="0.45516467"
inkscape:label="leg-l" /><circle
style="opacity:1;fill:#ff01e9;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
id="circle31"
cx="812.82855"
cy="599.16565"
r="0.37521484"
inkscape:label="leg-r-pivot"
transform="translate(-0.78025742,-1.094282)" /></g><g
id="g10"
inkscape:label="leg-l-transform"
transform="translate(-0.7818143,1.1896549)"><rect
style="opacity:1;fill:#4e60da;fill-opacity:1;stroke:#390000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="rect3"
width="2.1739705"
height="5.6944928"
x="805.39172"
y="595.75208"
ry="0.55225909"
inkscape:label="leg-l" /><rect
style="opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="rect8"
width="2.0801392"
height="0.72460753"
x="805.35431"
y="601.8493"
ry="0.36230376"
inkscape:label="leg-l" /><circle
style="opacity:1;fill:#ff01e9;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
id="circle30"
cx="805.76044"
cy="599.16565"
r="0.37521484"
inkscape:label="leg-l-pivot"
transform="translate(0.7818143,-1.1896549)" /></g><g
id="g6"
transform="matrix(-0.78898412,0.61441359,0.61441359,0.78898412,1087.3482,-367.58729)"
inkscape:label="arm-r-transform"><g
id="g29"
inkscape:label="arm-r-graphics"><rect
style="opacity:1;fill:#da4e4e;fill-opacity:1;stroke:#390000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="rect6"
width="2.1739705"
height="5.6944928"
x="805.12994"
y="590.17987"
ry="0.55225909"
inkscape:label="leg-l" /><circle
style="opacity:1;fill:#f6e9c3;fill-opacity:1;stroke:#390000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="circle6"
cx="806.1554"
cy="596.2511"
r="1.2602556" /></g><circle
style="opacity:1;fill:#ff01e9;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
id="circle29"
cx="283.21512"
cy="961.21893"
r="0.37521484"
inkscape:label="arm-r-pivot"
transform="matrix(-0.78898412,0.61441359,0.61441359,0.78898412,438.93525,-341.91227)" /></g><rect
style="opacity:1;fill:#949494;fill-opacity:1;stroke:#390000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="rect1"
width="9.695344"
height="8.6811571"
x="804.41333"
y="591.01855"
ry="3.3052826"
inkscape:label="body-graphics" /><g
id="g5"
transform="rotate(37.909323,800.74065,589.16456)"
inkscape:label="arm-l-transform"><g
id="g30"
inkscape:label="arm-l-graphics"><rect
style="opacity:1;fill:#4e60da;fill-opacity:1;stroke:#390000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="rect5"
width="2.1739705"
height="5.6944928"
x="805.12994"
y="590.17987"
ry="0.55225909"
inkscape:label="leg-l" /><circle
style="opacity:1;fill:#f6e9c3;fill-opacity:1;stroke:#390000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="path5"
cx="806.1554"
cy="596.2511"
r="1.2602556" /></g><circle
style="opacity:1;fill:#ff01e9;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
id="path26"
cx="804.17902"
cy="593.55609"
r="0.37521484"
inkscape:label="arm-l-pivot"
transform="rotate(-37.909323,800.74065,589.16456)" /></g><g
id="g13"
inkscape:label="head-transform"
transform="translate(0.1164391,1.5636286)"
style="fill:#f6e9c3;fill-opacity:1;stroke:#390000;stroke-opacity:1"><rect
style="display:inline;opacity:1;fill:#f6e9c3;fill-opacity:1;stroke:#390000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="rect7"
width="12.503357"
height="12.178973"
x="803.11865"
y="578.26825"
ry="4.191853"
inkscape:label="head-graphics" /><g
id="g23"
inkscape:label="hair-transform"
style="display:inline"><g
id="g31"
inkscape:label="hair-graphics"><path
style="opacity:1;fill:#da7300;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 802.71715,590.06034 1.85722,-0.95514 c 0,0 -1.03344,-5.71606 3.3704,-5.65003 1.75987,0.0264 2.43899,1.16705 2.43899,1.16705 0,0 2.58007,-1.3539 3.74746,0.52985 1.1674,1.88375 3.01917,1.06117 3.01917,1.06117 0,0 2.22866,-5.57165 -1.0082,-8.22482 -3.23687,-2.65317 -7.69419,-2.97155 -7.69419,-2.97155 l -3.68538,2.05127 c 0,0 0.81995,-2.84722 -0.93114,-2.82069 -1.75109,0.0265 -1.88375,0.66329 -1.88375,0.66329 l 0.19026,1.39279 -1.83522,0.22564 c 0,0 -4.4308,1.1674 -1.6715,1.85722 2.75929,0.68983 3.17494,0.28828 3.17494,0.28828 l -2.19632,2.92203 c 0,0 1.56842,6.63292 2.57663,8.17176 1.0082,1.53884 0.53063,0.29185 0.53063,0.29185 z"
id="path20"
sodipodi:nodetypes="ccscscsccscccsccscc"
inkscape:label="hair"
transform="translate(-0.1164391,-1.5636286)" /></g><circle
style="opacity:1;fill:#ff01e9;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
id="circle34"
cx="809.72156"
cy="582.39331"
r="0.37521484"
inkscape:label="hair-pivot" /></g><g
id="g21"
inkscape:label="eye-r-transform"
transform="translate(-0.1164391,-1.5636286)"
style="display:inline"><g
id="g24"
inkscape:label="eye-r-graphics"><g
id="g15"
transform="translate(-0.53966646,1.3539564)"
style="display:inline"><rect
style="fill:#ccbf9b;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
id="rect20-8"
width="2.7725384"
height="2.4276414"
x="811.87024"
y="586.84436"
ry="1.147486" /><rect
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="rect13"
width="2.8704028"
height="3.7896748"
x="811.81482"
y="584.88489"
ry="1.4352014" /><rect
style="opacity:1;fill:#8c3532;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="rect14"
width="2.0146508"
height="2.6009443"
x="811.84686"
y="585.59167"
ry="1.0412227" /><rect
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.36252;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="rect15"
width="0.81217694"
height="0.90432769"
x="812.11621"
y="585.82965"
ry="0.4476451" /></g><path
style="fill:none;fill-opacity:1;stroke:#390000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
d="m 814.20504,586.82076 c -0.56241,-0.98423 -1.41579,-1.07305 -2.5578,-0.54632"
id="path19"
sodipodi:nodetypes="cc" /></g><circle
style="opacity:1;fill:#ff01e9;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
id="circle32"
cx="812.883"
cy="588.36292"
r="0.37521484"
inkscape:label="eye-r-pivot" /></g><g
id="g22"
inkscape:label="eye-l-transform"
style="display:inline;fill:#f6e9c3;fill-opacity:1;stroke:#390000;stroke-opacity:1"
transform="translate(-0.1164391,-1.5636286)"><g
id="g25"
inkscape:label="eye-l-graphics"><rect
style="fill:#ccbf9b;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
id="rect20"
width="2.7725384"
height="2.4276414"
x="806.83789"
y="588.34387"
ry="1.147486" /><rect
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="rect16"
width="2.8704028"
height="3.7896748"
x="806.81653"
y="586.41492"
ry="1.4352014" /><rect
style="fill:#8c3532;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="rect17"
width="2.0146508"
height="2.6009443"
x="807.66833"
y="587.09528"
ry="1.0412227" /><rect
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.29019;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="rect18"
width="0.76906282"
height="0.85632181"
x="807.98566"
y="587.43658"
ry="0.42388204" /><path
style="fill:none;fill-opacity:1;stroke:#390000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
d="m 806.82122,586.87365 c 0.56241,-0.98423 1.41579,-1.07305 2.5578,-0.54632"
id="path18"
sodipodi:nodetypes="cc" /></g><circle
style="opacity:1;fill:#ff01e9;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
id="circle33"
cx="808.24622"
cy="588.36292"
inkscape:label="eye-l-pivot"
r="0.37521484" /></g><circle
style="opacity:1;fill:#ff01e9;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
id="circle27"
cx="809.37781"
cy="590.43231"
r="0.37521484"
inkscape:label="head-pivot" /></g><circle
style="opacity:1;fill:#ff01e9;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
id="circle28"
cx="809.2298"
cy="600.00842"
r="0.37521484"
inkscape:label="body-pivot"
sodipodi:insensitive="true" /></g><circle
style="opacity:1;fill:#ff01e9;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
id="circle26"
cx="809.2298"
cy="604.21301"
r="0.37521484"
inkscape:label="root-pivot" /></g></svg>

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -16,7 +16,8 @@
<p class="subtitle"> <p class="subtitle">
File tree rename/delete, locale generator rewrite, locale documentation, File tree rename/delete, locale generator rewrite, locale documentation,
doc/ renamed to workspace/, fixed navigation header system, doc/ renamed to workspace/, fixed navigation header system,
Guides/Actions workspace restructure, and Update History action rewrite. Guides/Actions workspace restructure, Update History action rewrite,
and Rojo chibi bone animator prototype.
</p> </p>
</header> </header>
@ -181,8 +182,6 @@
</div> </div>
</div> </div>
</section>
<div class="card"> <div class="card">
<h3>Update History Action: Commit and Push</h3> <h3>Update History Action: Commit and Push</h3>
<p> <p>
@ -205,6 +204,44 @@
</div> </div>
</div> </div>
<div class="card">
<h3>Rojo Chibi Bone Animator Prototype</h3>
<p>
A 2D skeletal animation prototype was started for an in-app chibi character
intended to accompany AI agent interactions. The source artwork is an Inkscape
SVG (<code>src/rojos/rojo-base.svg</code>) where each body part is a
<code>&lt;g&gt;</code> labelled <code>*-transform</code> and contains a
<code>&lt;circle&gt;</code> labelled <code>*-pivot</code> that marks the
rotation point. The pivot circle carries an inverse transform so its
<code>cx/cy</code> sits at the exact world-space pivot regardless of the
bone's own transform.
</p>
<p style="margin-top:0.75rem">
<code>src/rojos/rojo-animator.ts</code> provides <code>RojoAnimator</code>,
which walks all <code>*-transform</code> groups on load, applies the pivot
circle's transform to its <code>cx/cy</code> to recover the local-space pivot,
removes the circles, and strips Inkscape namespaced attributes. At runtime,
<code>setBoneAngle(name, degrees)</code> writes
<code>restTransform translate(lx,ly) rotate(θ) translate(-lx,-ly)</code>
directly onto the group element — no graphics library required. The parser
handles all three transform types present in the SVG:
<code>translate</code>, <code>matrix</code>, and <code>rotate(angle,cx,cy)</code>.
</p>
<p style="margin-top:0.75rem">
<code>public/rojos/rojo-demo.html</code> fetches the SVG, injects it into
the page, and runs a <code>requestAnimationFrame</code> loop with three
switchable animations: <em>Idle</em> (breathing sway), <em>Walk</em>
(legs and arms in opposite phase), and <em>Wave</em> (right arm wave).
</p>
<div class="tags">
<span class="tag">src/rojos/rojo-animator.ts</span>
<span class="tag">public/rojos/rojo-demo.html</span>
<span class="tag">public/rojos/rojo-base.svg</span>
<span class="tag">SVG bone animation</span>
<span class="tag">no graphics library</span>
</div>
</div>
</section> </section>
<section> <section>
@ -292,7 +329,12 @@
<code>server/localeGenerator.ts</code> — complete rewrite (per-directory output)<br> <code>server/localeGenerator.ts</code> — complete rewrite (per-directory output)<br>
<code>src/locales/generated/</code> — new: generated locale classes<br> <code>src/locales/generated/</code> — new: generated locale classes<br>
<code>src/locales/LocaleManager.ts</code><code>static $</code> changed to <code>static get $</code><br> <code>src/locales/LocaleManager.ts</code><code>static $</code> changed to <code>static get $</code><br>
<code>CLAUDE.md</code> — updated to point to <code>workspace/outline/index.html</code> <code>CLAUDE.md</code> — updated to point to <code>workspace/outline/index.html</code><br>
<code>workspace/index.html</code> — updated: added Guides and Actions cards<br>
<code>src/rojos/rojo-base.svg</code> — new: Inkscape chibi character with bone/pivot structure<br>
<code>src/rojos/rojo-animator.ts</code> — new: SVG bone animator (pivot parser + setBoneAngle)<br>
<code>public/rojos/rojo-base.svg</code> — new: copy of SVG for static serving<br>
<code>public/rojos/rojo-demo.html</code> — new: interactive animation demo (idle/walk/wave)
</p> </p>
</div> </div>
</section> </section>

View File

@ -21,7 +21,7 @@
<div class="card"> <div class="card">
<h3><a href="2026/07-July/06-Monday/index.html">Monday, 6 July 2026</a></h3> <h3><a href="2026/07-July/06-Monday/index.html">Monday, 6 July 2026</a></h3>
<p>File tree rename/delete (right-click context menu), locale generator rewrite (per-directory output, new naming conventions), locale documentation, doc/ renamed to workspace/, fixed navigation header system, Guides/Actions workspace restructure, Update History action rewrite (commit/push with confirmation).</p> <p>File tree rename/delete (right-click context menu), locale generator rewrite (per-directory output, new naming conventions), locale documentation, doc/ renamed to workspace/, fixed navigation header system, Guides/Actions workspace restructure, Update History action rewrite, Rojo chibi bone animator prototype (SVG pivot system, no graphics library).</p>
</div> </div>
<div class="card"> <div class="card">

View File

@ -22,14 +22,33 @@
<div class="card"> <div class="card">
<h3>Outline</h3> <h3>Outline</h3>
<p> <p>
Explains Roject's goals, features and future plans and actions that are used for Explains Roject's goals, features and future plans for the project.
the project.
<br> <br>
<a href="./outline/index.html">Read more about the outline</a> <a href="./outline/index.html">Read more about the outline</a>
</p> </p>
</div> </div>
<div class="card">
<h3>Guides</h3>
<p>
Context, rules, workflows and examples for how to do things.
<br>
<a href="./guides/index.html">Read more about the guides</a>
</p>
</div>
<div class="card">
<h3>Actions</h3>
<p>
Named tasks and steps for recurring topics.
<br>
<a href="./actions/index.html">Read more about the actions</a>
</p>
</div>
<div class="card"> <div class="card">
<h3>History</h3> <h3>History</h3>
<p> <p>
@ -40,6 +59,11 @@
</p> </p>
</div> </div>
<div>
To get the full picture, follow each page (as human or agent).
</div>
</section> </section>