Ported from the thaiai.lol easter egg (thaiai iso/hamster/lib/hamster.rhai); the data-centre joke parts stay there. - hamster (Animals): golden, grey or cream with a white belly, pink nose, ears and paws, buck teeth and whiskers; standing, running on the spot or asleep (eyes shut, z Z); n and spread like the herds; life size (about 17 cm) at scale 1, up to 40 times. - hamster_wheel (Animals): diameter (0.3 m life size, up to 12 m), running, A-frame stand or a cage-wall axle stub, fur colour. Running legs and the rungs are flipbooks of 4 poses, 0.1 s each; the spokes turn by a CSS rotation in a matrix that maps the wheel's plane onto the screen. - draw_parts learns loft2 (two-tone lofts), eye and flip parts; place_parts scales and moves any part list; loft and loft2 share loft_rings (the other animals draw byte for byte as before). - Things under 2 m are drawn ten times bigger in a group that scales them back (fine_begin / fine_end), so a life-size hamster keeps its shape on the drawing's 0.1-unit grid; sort boxes stay real size. - The legs push back on the ground and swing forward lifted (the original lifted them on the way back). - Theme: hamster, hamsterg, hamsterc, hamsterw, hamsterp, hwheel, hwhub, whisker and hamz in both palettes, materials, rules for eyes, whiskers, rims, spokes, hub, rungs and the z Z. Viewer CSS: iso-flip4, iso-wheelspin, iso-runbob, iso-zzz; beast edges take --bw (default .3). - Test hamsters_run_in_wheels_from_every_side: no sort cycles in any rotation, flipbook counts, scaled groups, real-size sort boxes, tooltips. - README, ROADMAP, SCRIPTING and the MCP instructions list them.
227 lines
16 KiB
CSS
227 lines
16 KiB
CSS
/* isokit-view.css: structure of the isokit viewer (stage, toolbar, tooltip,
|
|
faces, tags, selection, animation, full screen). Colours come from the
|
|
theme (palette variables --iso-*); this file holds no scene colours.
|
|
Ported from the CNX Vinyl / CNX Scuba / Mooban views. */
|
|
|
|
.iso-wrap { position: relative; color: var(--iso-tipfg); }
|
|
|
|
/* stage, toolbar, tooltip, note */
|
|
.iso-stage {
|
|
position: relative; width: 100%; aspect-ratio: 16 / 10; min-height: 340px; max-height: 86vh;
|
|
overflow: hidden; border-radius: 8px; border: 1px solid var(--iso-btnline);
|
|
background:
|
|
radial-gradient(1px 1px at 12% 18%, var(--iso-star) 50%, transparent 51%),
|
|
radial-gradient(1px 1px at 33% 9%, var(--iso-star) 50%, transparent 51%),
|
|
radial-gradient(1px 1px at 58% 14%, var(--iso-star) 50%, transparent 51%),
|
|
radial-gradient(1.2px 1.2px at 81% 7%, var(--iso-star) 50%, transparent 51%),
|
|
radial-gradient(1px 1px at 91% 22%, var(--iso-star) 50%, transparent 51%),
|
|
linear-gradient(180deg, var(--iso-sky1), var(--iso-sky2));
|
|
}
|
|
.iso-svg { display: block; width: 100%; height: 100%; touch-action: pan-y; cursor: grab; user-select: none; -webkit-user-select: none; outline: none; }
|
|
/* its own compositor layer: pan and zoom gestures move it by a transform (no repaint) until they end;
|
|
drawn past its box so the ground just outside the view is there while panning (the stage clips) */
|
|
.iso-svg { will-change: transform; transform-origin: 0 0; overflow: visible; }
|
|
.iso-dragging .iso-svg { cursor: grabbing; }
|
|
.iso-svg:focus-visible { outline: 2px solid var(--iso-accent); outline-offset: -2px; }
|
|
.iso-bar {
|
|
position: absolute; top: 8px; left: 8px; right: 8px; display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
|
|
pointer-events: none; font: 13px/1.2 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
}
|
|
.iso-bar > *, .iso-bar button { pointer-events: auto; }
|
|
.iso-bar button, .iso-compass {
|
|
background: var(--iso-btn); color: var(--iso-btnfg); border: 1px solid var(--iso-btnline); border-radius: 5px;
|
|
padding: 5px 10px; font: inherit; cursor: pointer; min-width: 32px; min-height: 30px;
|
|
}
|
|
.iso-bar button:hover { border-color: var(--iso-accent); }
|
|
.iso-bar button:focus-visible { outline: 2px solid var(--iso-accent); outline-offset: 1px; }
|
|
.iso-seg { display: inline-flex; }
|
|
.iso-seg button { border-radius: 0; }
|
|
.iso-seg button:first-child { border-radius: 5px 0 0 5px; }
|
|
.iso-seg button:last-child { border-radius: 0 5px 5px 0; }
|
|
.iso-seg button + button { border-left: 0; }
|
|
.iso-seg button[aria-pressed=true] { background: var(--iso-btnon); border-color: var(--iso-accent); color: var(--iso-btnfg); font-weight: 600; }
|
|
.iso-compass { display: inline-flex; align-items: center; gap: 2px; padding: 3px 7px; cursor: default; }
|
|
.iso-compass svg { width: 20px; height: 20px; }
|
|
.iso-compass path { fill: var(--iso-accent); }
|
|
.iso-compass b { font-size: 11px; }
|
|
.iso-tip {
|
|
position: absolute; left: 0; top: 0; z-index: 3; max-width: min(330px, calc(100% - 12px)); padding: 8px 10px; pointer-events: none;
|
|
will-change: transform; contain: layout style;
|
|
background: var(--iso-tipbg); color: var(--iso-tipfg); border: 1px solid var(--iso-tipline); border-radius: 6px;
|
|
font: 13px/1.4 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; box-shadow: 0 4px 14px rgba(0, 0, 0, .28);
|
|
}
|
|
.iso-tip b { display: block; font-weight: 600; margin-bottom: 2px; }
|
|
.iso-tip span { display: block; color: var(--iso-tipmute); font-size: 12px; }
|
|
.iso-tip table { margin-top: 4px; border-collapse: collapse; font-size: 12px; }
|
|
.iso-tip th { text-align: left; font-weight: 400; color: var(--iso-tipmute); padding: 1px 10px 1px 0; }
|
|
.iso-tip td { text-align: right; font-variant-numeric: tabular-nums; }
|
|
.iso-tip[hidden], .iso-wrap [hidden] { display: none !important; }
|
|
.iso-note { margin: 6px 2px 0; font: 12px/1.4 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; color: var(--iso-mute); }
|
|
.iso-err {
|
|
position: absolute; left: 8px; right: 8px; bottom: 8px; z-index: 4; padding: 8px 10px; border-radius: 6px;
|
|
background: var(--iso-warnbg); color: var(--iso-warnfg); border: 1px solid var(--iso-warnline);
|
|
font: 13px/1.4 ui-monospace, "Cascadia Mono", Consolas, monospace;
|
|
}
|
|
.iso-err b { display: block; font: 600 13px/1.4 system-ui, sans-serif; }
|
|
.iso-err small { display: block; opacity: .8; margin-top: 3px; }
|
|
.iso-loading .iso-svg { opacity: .92; }
|
|
|
|
/* faces: material colour --b mixed towards the shade per face direction */
|
|
.iso-svg .iso-t { fill: var(--b, #888); }
|
|
.iso-svg .iso-l { fill: color-mix(in srgb, var(--b, #888) var(--iso-kl), var(--iso-shade)); }
|
|
.iso-svg .iso-c { fill: color-mix(in srgb, var(--b, #888) var(--iso-kc), var(--iso-shade)); }
|
|
.iso-svg .iso-r { fill: color-mix(in srgb, var(--b, #888) var(--iso-kr), var(--iso-shade)); }
|
|
.iso-sprites path, .iso-sprites ellipse, .iso-sprites circle { stroke: var(--iso-edge); stroke-width: var(--iso-edgew); stroke-linejoin: round; }
|
|
.iso-ground path, .iso-ground ellipse { stroke: none; }
|
|
/* animals: each face edged in its own shade (no seams, no wireframe), so a body reads as one surface */
|
|
.iso-sprites .iso-beast { stroke-width: var(--bw, .3); }
|
|
.iso-sprites .iso-beast.iso-t { stroke: var(--b, #888); }
|
|
.iso-sprites .iso-beast.iso-l { stroke: color-mix(in srgb, var(--b, #888) var(--iso-kl), var(--iso-shade)); }
|
|
.iso-sprites .iso-beast.iso-c { stroke: color-mix(in srgb, var(--b, #888) var(--iso-kc), var(--iso-shade)); }
|
|
.iso-sprites .iso-beast.iso-r { stroke: color-mix(in srgb, var(--b, #888) var(--iso-kr), var(--iso-shade)); }
|
|
@supports not (color: color-mix(in srgb, red 50%, blue)) {
|
|
.iso-svg .iso-l, .iso-svg .iso-c, .iso-svg .iso-r { fill: var(--b, #888); }
|
|
.iso-svg .iso-r { fill-opacity: .82; }
|
|
}
|
|
|
|
/* ghosting (levels above a building) */
|
|
.iso-svg .iso-dim { opacity: .4; }
|
|
html[data-theme=light] .iso-svg .iso-dim { opacity: .5; }
|
|
.iso-svg .iso-dim .iso-a { animation: none !important; }
|
|
|
|
/* tags (counter-scaled by --iso-k so they keep their screen size) */
|
|
.iso-tag path { fill: var(--iso-tagbg); stroke: var(--iso-tagline); stroke-width: 1; }
|
|
.iso-tag text { fill: var(--iso-tagfg); font: 600 10.5px/1 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; text-anchor: middle; }
|
|
.iso-signtag path { fill: var(--iso-signbg); stroke: var(--iso-signline); }
|
|
.iso-signtag text { fill: var(--iso-signfg); }
|
|
.iso-warntag path { fill: var(--iso-warnbg); stroke: var(--iso-warnline); }
|
|
.iso-warntag text { fill: var(--iso-warnfg); }
|
|
.iso-tag { cursor: default; }
|
|
.iso-svg .iso-hide { display: none; }
|
|
.iso-svg .iso-ghost { opacity: .38; }
|
|
|
|
/* hover and selection; .iso-osel marks the object selected in the Studio */
|
|
.iso-svg .iso-hov > path, .iso-svg .iso-hov > ellipse, .iso-svg path.iso-hov { stroke: var(--iso-hov); stroke-width: .8; }
|
|
.iso-svg .iso-sel > path, .iso-svg .iso-sel > ellipse, .iso-svg .iso-sel > g > path, .iso-svg path.iso-sel { stroke: var(--iso-sel); stroke-width: 1.1; }
|
|
.iso-svg .iso-osel path, .iso-svg .iso-osel ellipse, .iso-svg .iso-osel circle { stroke: var(--iso-sel); stroke-width: 1.2; }
|
|
.iso-svg .iso-moving { opacity: .75; }
|
|
|
|
/* animation: CSS only; paused off-screen (iso-off), when stopped (iso-still) and for reduced motion */
|
|
.iso-svg .iso-fan { transform-box: fill-box; transform-origin: center; animation: iso-spin 1.1s linear infinite; }
|
|
.iso-svg .iso-puff, .iso-svg .iso-puff2, .iso-svg .iso-smoke { transform-box: fill-box; transform-origin: center; animation: iso-puff 3.2s ease-out infinite; opacity: 0; pointer-events: none; stroke: none; }
|
|
.iso-svg .iso-puff, .iso-svg .iso-puff2 { fill: var(--iso-steam); }
|
|
.iso-svg .iso-smoke { fill: var(--iso-smoke); }
|
|
.iso-svg .iso-glint { animation: iso-glint 7s ease-in-out infinite; display: none; }
|
|
html[data-theme=light] .iso-svg .iso-glint { display: inline; }
|
|
.iso-svg .iso-disc { animation: iso-move 16s linear infinite; }
|
|
.iso-svg .iso-walk { animation: iso-shuttle 70s ease-in-out infinite; }
|
|
.iso-svg .iso-shuttle { animation: iso-shuttle 48s ease-in-out infinite; }
|
|
.iso-svg .iso-drive { animation: iso-drive 16s linear infinite; }
|
|
.iso-svg .iso-ledr, .iso-svg .iso-ledo { animation: iso-blink 2.4s ease-in-out infinite; }
|
|
/* fish: each turns round its pond's middle (the box is centred there), at its own speed */
|
|
.iso-svg .iso-swim { transform-box: fill-box; transform-origin: center; animation: iso-spin 20s linear infinite; }
|
|
.iso-svg .iso-rip { transform-box: fill-box; transform-origin: center; animation: iso-rip 5.1s ease-out infinite; }
|
|
/* conveyors: the goods move one spacing per loop and start again, the first fading in, the last out;
|
|
presses go down once per item */
|
|
.iso-svg .iso-ride { animation: iso-move 2s linear infinite; }
|
|
.iso-svg .iso-ridein { animation: iso-fadein 2s linear infinite; }
|
|
.iso-svg .iso-rideout { animation: iso-fadeout 2s linear infinite; }
|
|
.iso-svg .iso-rideio { animation: iso-fadeio 2s linear infinite; }
|
|
.iso-svg .iso-ram { animation: iso-ram 2s ease-in-out infinite; }
|
|
/* robot arms: a flipbook of 16 poses, each shown for a sixteenth of the cycle (delays set the turn);
|
|
unanimated (reduced motion, ghosted) only the first */
|
|
.iso-svg .iso-flip { visibility: hidden; animation: iso-flip16 4s step-end infinite; }
|
|
.iso-svg .iso-flip.iso-pose0 { visibility: visible; }
|
|
/* animals: two poses taking turns (grazing, looking up; delays set the turn); unanimated the first.
|
|
Birds flap and butterflies flutter (wings scaled across their span); rabbits hop now and then */
|
|
.iso-svg .iso-pose2 { visibility: hidden; animation: iso-pose2b 9s step-end infinite; }
|
|
.iso-svg .iso-pose2.iso-pose0 { visibility: visible; animation-name: iso-pose2a; }
|
|
.iso-svg .iso-flap { transform-box: fill-box; transform-origin: center; animation: iso-flap .8s ease-in-out infinite alternate; }
|
|
.iso-svg .iso-flutter { transform-box: fill-box; transform-origin: center; animation: iso-flap .3s ease-in-out infinite alternate; }
|
|
.iso-svg .iso-hop { animation: iso-hop 3.4s ease-out infinite; }
|
|
/* hamsters: running legs and wheel rungs are flipbooks of 4 poses, 0.1 s each (delays set the turn;
|
|
unanimated the first); spokes turn in the wheel's plane; runners bob, sleepers snore */
|
|
.iso-svg .iso-flip4 { visibility: hidden; animation: iso-flip4 .4s step-end infinite; }
|
|
.iso-svg .iso-flip4.iso-pose0 { visibility: visible; }
|
|
.iso-svg .iso-wheelspin { transform-box: fill-box; transform-origin: center; animation: iso-spin 9.6s linear infinite; }
|
|
.iso-svg .iso-runbob { animation: iso-runbob .4s ease-in-out infinite; }
|
|
.iso-svg .iso-zzz { animation: iso-zzz 3.2s ease-out infinite; }
|
|
.iso-svg .iso-park { display: none; }
|
|
/* route movers: each piece's copy shows only while a mover is on it (keyframes made per piece);
|
|
not .iso-a, so unanimated views pause them where they are instead of hiding them */
|
|
.iso-svg .iso-travel { visibility: hidden; }
|
|
/* paused off-screen (iso-off), when stopped (iso-still), with animation off and during pan/zoom gestures */
|
|
.iso-off .iso-a, .iso-still .iso-a, .iso-off .iso-drive, .iso-still .iso-drive, .iso-off .iso-ledr, .iso-off .iso-ledo, .iso-still .iso-ledr, .iso-still .iso-ledo,
|
|
.iso-anim-off .iso-a, .iso-anim-off .iso-drive, .iso-anim-off .iso-ledr, .iso-anim-off .iso-ledo,
|
|
.iso-gesture .iso-a, .iso-gesture .iso-drive, .iso-gesture .iso-ledr, .iso-gesture .iso-ledo,
|
|
.iso-off .iso-travel, .iso-still .iso-travel, .iso-anim-off .iso-travel, .iso-gesture .iso-travel, .iso-svg .iso-dim .iso-travel { animation-play-state: paused !important; }
|
|
.iso-still .iso-park, .iso-anim-off .iso-park { display: inline; }
|
|
.iso-animbtn { min-width: 30px; font-size: 13px; }
|
|
.iso-bar [hidden] { display: none !important; }
|
|
@keyframes iso-spin { to { transform: rotate(360deg); } }
|
|
@keyframes iso-puff {
|
|
0% { transform: translate(0, 0) scale(.35); opacity: 0; }
|
|
12% { opacity: .75; }
|
|
100% { transform: translate(5px, -28px) scale(2); opacity: 0; }
|
|
}
|
|
@keyframes iso-rip { 0% { transform: scale(.2); opacity: 0; } 15% { opacity: .9; } 100% { transform: scale(1.5); opacity: 0; } }
|
|
@keyframes iso-glint { 0%, 84%, 100% { opacity: 0; } 91% { opacity: .9; } }
|
|
@keyframes iso-blink { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }
|
|
@keyframes iso-fadein { 0% { opacity: 0; } 35%, 100% { opacity: 1; } }
|
|
@keyframes iso-fadeout { 0%, 65% { opacity: 1; } 100% { opacity: 0; } }
|
|
@keyframes iso-fadeio { 0%, 100% { opacity: 0; } 30%, 70% { opacity: 1; } }
|
|
@keyframes iso-flip16 { 0% { visibility: visible; } 6.25%, 100% { visibility: hidden; } }
|
|
@keyframes iso-pose2a { 0% { visibility: visible; } 65%, 100% { visibility: hidden; } }
|
|
@keyframes iso-pose2b { 0% { visibility: hidden; } 65%, 100% { visibility: visible; } }
|
|
@keyframes iso-flap { from { transform: scaleX(1); } to { transform: scaleX(.25); } }
|
|
@keyframes iso-hop { 0%, 70%, 100% { transform: translate(0, 0); } 78% { transform: translate(0, -4px); } 86% { transform: translate(0, 0); } 91% { transform: translate(0, -2px); } }
|
|
@keyframes iso-flip4 { 0% { visibility: visible; } 25%, 100% { visibility: hidden; } }
|
|
@keyframes iso-runbob { 0%, 100% { transform: translate(0, 0); } 50% { transform: translate(0, var(--hb, -1px)); } }
|
|
@keyframes iso-zzz { 0% { transform: translate(0, 0); opacity: 0; } 20% { opacity: 1; } 100% { transform: translate(var(--zx, 3px), var(--zy, -7px)); opacity: 0; } }
|
|
@keyframes iso-ram { 0%, 20%, 100% { transform: translate(0, 0); } 45%, 55% { transform: translate(var(--dx), var(--dy)); } }
|
|
@keyframes iso-move { from { transform: translate(0, 0); } to { transform: translate(var(--dx), var(--dy)); } }
|
|
@keyframes iso-shuttle {
|
|
0%, 8% { transform: translate(0, 0); }
|
|
46%, 56% { transform: translate(var(--dx), var(--dy)); }
|
|
94%, 100% { transform: translate(0, 0); }
|
|
}
|
|
@keyframes iso-drive {
|
|
0% { transform: translate(0, 0); opacity: 0; }
|
|
5% { opacity: 1; }
|
|
92% { transform: translate(var(--dx), var(--dy)); opacity: 1; }
|
|
96%, 100% { transform: translate(var(--dx), var(--dy)); opacity: 0; }
|
|
}
|
|
/* a system asking for less motion: still pictures, until the viewer turns animation on (iso-anim-on) */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.iso-wrap:not(.iso-anim-on) .iso-svg .iso-a, .iso-wrap:not(.iso-anim-on) .iso-svg .iso-ledr, .iso-wrap:not(.iso-anim-on) .iso-svg .iso-ledo { animation: none !important; }
|
|
.iso-wrap:not(.iso-anim-on) .iso-svg .iso-travel { animation-play-state: paused !important; }
|
|
.iso-wrap:not(.iso-anim-on) .iso-svg :is(.iso-puff, .iso-puff2, .iso-smoke, .iso-disc, .iso-glint, .iso-rip) { display: none; }
|
|
.iso-wrap:not(.iso-anim-on) .iso-svg .iso-park { display: inline; }
|
|
}
|
|
@media (max-width: 640px) {
|
|
.iso-stage { aspect-ratio: 1 / 1; min-height: 320px; }
|
|
.iso-bar { gap: 3px; top: 6px; left: 6px; right: 6px; }
|
|
.iso-bar button { padding: 4px 6px; min-width: 26px; min-height: 30px; }
|
|
.iso-compass { padding: 3px; min-height: 30px; }
|
|
.iso-compass b { display: none; }
|
|
}
|
|
@media (max-width: 370px) { .iso-compass { display: none; } }
|
|
|
|
/* full screen: native (:fullscreen) or pinned over the page where the Fullscreen API is missing (iPhone) */
|
|
.iso-fs { display: flex !important; flex-direction: column; box-sizing: border-box; overflow: hidden; }
|
|
.iso-fs-fake { position: fixed !important; inset: 0; z-index: 2147483000; margin: 0 !important; border-radius: 0 !important;
|
|
width: auto !important; height: auto !important; max-width: none !important; }
|
|
.iso-wrap.iso-fs { background: var(--iso-fsbg, #0e1117); padding: 0; }
|
|
html[data-theme=light] .iso-wrap.iso-fs { --iso-fsbg: #eef3f9; }
|
|
html.iso-fs-lock, html.iso-fs-lock body { overflow: hidden !important; }
|
|
.iso-fs .iso-fs-grow, .iso-fs .iso-wrap, .iso-wrap.iso-fs { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; margin: 0; }
|
|
.iso-fs .iso-stage { flex: 1 1 auto; aspect-ratio: auto; max-height: none; min-height: 0; height: auto; }
|
|
.iso-fs .iso-note { display: none; }
|
|
.iso-fullbtn svg { display: block; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
|
|
.iso-fullbtn .iso-fs-out, .iso-fs .iso-fullbtn .iso-fs-in { display: none; }
|
|
.iso-fs .iso-fullbtn .iso-fs-out { display: inline; }
|
|
|
|
/* fill a sized container instead of the 16:10 card (Studio) */
|
|
.iso-fill, .iso-fill > .iso-wrap { height: 100%; display: flex; flex-direction: column; }
|
|
.iso-fill .iso-stage { flex: 1 1 auto; aspect-ratio: auto; max-height: none; min-height: 0; border-radius: 0; border: 0; }
|