Release 0.6.1: 2D | 3D switch next to Export, 3D controls in the dock #20
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## 0.6.1 — 2026-10-12
|
||||
|
||||
- The 3D menu is replaced by a **2D | 3D** switch next to **Export**: the filled half is the view you are in, one click flips it.
|
||||
- The 3D-only controls moved to the bottom-right dock next to **PATH** and **LENS**: **STACK** (levels by kind, on or off), **TOP** (from above), and the zoom stepper's middle button reads **RESET** in 3D instead of a 2D zoom level.
|
||||
- Nothing is greyed out any more, and no menu stays open over the diagram after a choice.
|
||||
|
||||
## 0.6.0 — 2026-10-11
|
||||
|
||||
- **3D view:** a **3D ▾** menu in the diagram toolbar, styled like Archify's own menus, switches any diagram to 3D. Nodes become raised blocks topped with the node as Archify draws it, relationships become lit pipes along Archify's routes, and the canvas, frames, lanes and legend form the floor. Works for all five diagram kinds, in every theme and style.
|
||||
|
||||
@@ -72,21 +72,21 @@ The Archify CLI and the AI agents that use it write diagrams as JSON, named `nam
|
||||
|
||||
## 3D view
|
||||
|
||||
Every diagram can also be seen in 3D. Open the **3D ▾** menu next to **Export** in the diagram's toolbar and choose **3D**; choose **2D** to go back. The button wears a cyan ring while the 3D view is on.
|
||||
Every diagram can also be seen in 3D. The **2D | 3D** switch next to **Export** in the diagram's toolbar shows which view you are in; click it to flip.
|
||||
|
||||

|
||||
|
||||
The 3D view is built from the diagram Archify draws, so it looks the same: each node becomes a raised block whose top is the node as Archify draws it, relationships become pipes along Archify's routes with their colours, dashes and arrowheads, and the canvas, frames, lanes and legend form the floor. It works for all five diagram kinds, in every theme and style (Classic, Signal Flow, Blueprint, Editorial).
|
||||
|
||||
The menu has three groups:
|
||||
While 3D is on, three controls join the bottom-right dock next to **PATH** and **LENS**:
|
||||
|
||||
- **View**: 2D or 3D.
|
||||
- **Levels**: *Flat* keeps every node on the floor; *Stacked* lifts nodes into levels by kind: clients and external systems on top, security below them, services in the middle, data on the floor.
|
||||
- **Camera**: *Reset view* (or <kbd>0</kbd>) flies back to the whole diagram, wherever you have moved to; *From above* looks straight down, like 2D.
|
||||
- **STACK** lifts nodes into levels by kind: clients and external systems on top, security below them, services in the middle, data on the floor. Click again for the flat layout.
|
||||
- **TOP** looks straight down, like 2D.
|
||||
- **RESET** (the middle of the zoom stepper, or <kbd>0</kbd>) flies back to the whole diagram, wherever you have moved to.
|
||||
|
||||
Moving around:
|
||||
|
||||
- Drag to turn, right-drag to move. On a trackpad, scroll with two fingers to move in any direction and pinch to zoom; a mouse wheel zooms. The diagram's zoom buttons and the <kbd>+</kbd> and <kbd>-</kbd> keys work too, beyond the 2D limits.
|
||||
- Drag to turn, right-drag to move. On a trackpad, scroll with two fingers to move in any direction and pinch to zoom; a mouse wheel zooms. The dock's **−** and **+** and the <kbd>-</kbd> and <kbd>+</kbd> keys work too, beyond the 2D limits.
|
||||
- Click a block to focus it, as in 2D: the Semantic Passport opens and unrelated parts fade. Hover a block to see its one-hop trace. Double-click a block to fly to it, or empty space to show everything.
|
||||
|
||||
Animation follows 2D exactly, because the 3D view reads Archify's own animations as they play: the trace (relationships drawing in, nodes pulsing) when the diagram opens or 3D is chosen, the hover trace, and the flows from the Semantic Passport, Path and Lens. *Live* off, or reduced motion, keeps it still.
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 237 KiB After Width: | Height: | Size: 228 KiB |
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "archify-vscode-ext",
|
||||
"version": "0.6.0",
|
||||
"version": "0.6.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "archify-vscode-ext",
|
||||
"version": "0.6.0",
|
||||
"version": "0.6.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"three": "^0.186.1",
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"name": "archify-vscode-ext",
|
||||
"displayName": "Archify Diagram Viewer",
|
||||
"description": "Archify diagrams in VS Code. Write diagrams as .archify files in YAML: they open as the interactive diagram, with live updates, validation, templates and completion. Also previews Archify's own JSON files (*.architecture.json, …) and opens rendered Archify HTML.",
|
||||
"version": "0.6.0",
|
||||
"version": "0.6.1",
|
||||
"publisher": "root-at-skic",
|
||||
"license": "MIT",
|
||||
"icon": "media/icon.png",
|
||||
|
||||
+69
-167
@@ -4,9 +4,9 @@ import { DiagramModel, readModel } from './svgModel';
|
||||
import { STYLE } from './style';
|
||||
|
||||
/**
|
||||
* 3D mode for the Archify viewer. It adds a 3D menu to Archify's toolbar
|
||||
* and, when on, replaces the 2D canvas with a three.js stage built from the
|
||||
* live SVG. Archify keeps owning the diagram: clicks in 3D go to the SVG
|
||||
* 3D mode for the Archify viewer. It adds a 2D | 3D switch to Archify's
|
||||
* toolbar and, when on, replaces the 2D canvas with a three.js stage built
|
||||
* from the live SVG. Archify keeps owning the diagram: clicks in 3D go to the SVG
|
||||
* nodes, so focus, Semantic Passport, reach and lens work as in 2D; the
|
||||
* stage redraws whenever Archify changes the SVG, theme or preset, and
|
||||
* follows Archify's animations as they play.
|
||||
@@ -36,41 +36,6 @@ const MARGIN = 80;
|
||||
/** Texture pixels per SVG unit; more costs memory and upload time for little gain. */
|
||||
const TEXTURE_SCALE = 2;
|
||||
|
||||
type MenuValue = '2d' | '3d' | 'flat' | 'stacked' | 'reset' | 'top';
|
||||
|
||||
interface MenuEntry {
|
||||
value: MenuValue;
|
||||
title: string;
|
||||
hint: string;
|
||||
/** Radio group; actions have none. */
|
||||
group?: 'view' | 'levels';
|
||||
shortcut?: string;
|
||||
}
|
||||
|
||||
const MENU: { heading: string; entries: MenuEntry[] }[] = [
|
||||
{
|
||||
heading: 'View',
|
||||
entries: [
|
||||
{ value: '2d', group: 'view', title: '2D', hint: 'The diagram as Archify draws it' },
|
||||
{ value: '3d', group: 'view', title: '3D', hint: 'Blocks and pipes you can turn' },
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: 'Levels',
|
||||
entries: [
|
||||
{ value: 'flat', group: 'levels', title: 'Flat', hint: 'Every node on the floor' },
|
||||
{ value: 'stacked', group: 'levels', title: 'Stacked', hint: 'Clients on top, data at the bottom' },
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: 'Camera',
|
||||
entries: [
|
||||
{ value: 'reset', title: 'Reset view', hint: 'Show the whole diagram', shortcut: '0' },
|
||||
{ value: 'top', title: 'From above', hint: 'Look straight down, like 2D' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
class Viewer3D {
|
||||
private stage: Stage | undefined;
|
||||
@@ -83,8 +48,10 @@ class Viewer3D {
|
||||
/** Keeps Archify's zoom buttons usable in 3D (see holdZoomButtons). */
|
||||
private readonly zoomObserver = new MutationObserver(() => this.enableZoomButtons());
|
||||
private zoomDisabled = new Map<HTMLButtonElement, boolean>();
|
||||
private readonly button: HTMLButtonElement;
|
||||
private readonly menu: HTMLDivElement;
|
||||
private readonly toggle: HTMLButtonElement;
|
||||
private readonly stackButton: HTMLButtonElement;
|
||||
private readonly topButton: HTMLButtonElement;
|
||||
private readonly resetLabel: HTMLSpanElement;
|
||||
private ambientReplayed = false;
|
||||
private exportItem: HTMLButtonElement | undefined;
|
||||
private saved: SavedView;
|
||||
@@ -95,145 +62,79 @@ class Viewer3D {
|
||||
private readonly svg: SVGSVGElement,
|
||||
) {
|
||||
this.saved = (host.getState().view3d as SavedView | undefined) ?? {};
|
||||
({ button: this.button, menu: this.menu } = this.buildMenu());
|
||||
this.toggle = this.buildSwitch();
|
||||
({ stack: this.stackButton, top: this.topButton, reset: this.resetLabel } = this.buildNavControls());
|
||||
this.addExportItem();
|
||||
this.interceptViewerControls();
|
||||
if (this.saved.active ?? host.defaultView === '3d') this.setActive(true);
|
||||
else this.syncButtons();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------- chrome
|
||||
|
||||
/**
|
||||
* The 3D menu: a toolbar button with a dropdown, built from Archify's own
|
||||
* style-menu classes so it looks and behaves like the menus beside it.
|
||||
* The 2D | 3D switch: Export's twin in the toolbar. Both states are always
|
||||
* visible, the filled half is the current one, a click flips it.
|
||||
*/
|
||||
private buildMenu(): { button: HTMLButtonElement; menu: HTMLDivElement } {
|
||||
const wrap = document.createElement('div');
|
||||
wrap.className = 'preset-wrap archify-3d-wrap';
|
||||
private buildSwitch(): HTMLButtonElement {
|
||||
const button = document.createElement('button');
|
||||
button.id = 'btn-3d';
|
||||
button.type = 'button';
|
||||
button.title = '3D view: switch 2D / 3D, levels and camera';
|
||||
button.setAttribute('aria-label', '3D view options');
|
||||
button.setAttribute('aria-haspopup', 'menu');
|
||||
button.setAttribute('aria-expanded', 'false');
|
||||
button.setAttribute('aria-controls', 'archify-3d-menu');
|
||||
// Same make-up as Archify's Export button: a label and a chevron.
|
||||
button.innerHTML = '3D <span class="toolbar-icon toolbar-chevron" aria-hidden="true"></span>';
|
||||
|
||||
const menu = document.createElement('div');
|
||||
menu.id = 'archify-3d-menu';
|
||||
menu.className = 'preset-menu archify-3d-menu';
|
||||
menu.setAttribute('role', 'menu');
|
||||
menu.setAttribute('aria-label', 'View options');
|
||||
for (const section of MENU) {
|
||||
const heading = document.createElement('div');
|
||||
heading.className = 'preset-menu-heading';
|
||||
heading.setAttribute('role', 'presentation');
|
||||
heading.innerHTML = `<span>${section.heading}</span>`;
|
||||
menu.appendChild(heading);
|
||||
for (const entry of section.entries) {
|
||||
const option = document.createElement('button');
|
||||
option.type = 'button';
|
||||
option.className = 'preset-option archify-3d-option';
|
||||
option.tabIndex = -1;
|
||||
option.dataset.archify3d = entry.value;
|
||||
option.setAttribute('role', entry.group ? 'menuitemradio' : 'menuitem');
|
||||
if (entry.group) option.setAttribute('aria-checked', 'false');
|
||||
option.innerHTML =
|
||||
`<span class="archify-3d-swatch" aria-hidden="true"><span class="archify-3d-swatch-icon archify-3d-i-${entry.value}"></span></span>` +
|
||||
`<span class="preset-option-copy"><strong>${entry.title}</strong><small>${entry.hint}</small></span>` +
|
||||
(entry.group
|
||||
? '<span class="preset-option-check" aria-hidden="true"></span>'
|
||||
: `<kbd class="archify-3d-kbd" aria-hidden="true">${entry.shortcut ?? ''}</kbd>`);
|
||||
option.addEventListener('click', (event) => {
|
||||
event.stopPropagation();
|
||||
if (option.getAttribute('aria-disabled') === 'true') return;
|
||||
this.choose(entry.value);
|
||||
if (!entry.group) this.closeMenu();
|
||||
else this.syncButtons();
|
||||
});
|
||||
menu.appendChild(option);
|
||||
}
|
||||
}
|
||||
|
||||
button.setAttribute('role', 'switch');
|
||||
button.setAttribute('aria-checked', 'false');
|
||||
button.innerHTML =
|
||||
'<span class="archify-3d-seg" data-seg="2d" aria-hidden="true">2D</span>' +
|
||||
'<span class="archify-3d-seg" data-seg="3d" aria-hidden="true">3D</span>';
|
||||
button.addEventListener('click', (event) => {
|
||||
event.stopPropagation();
|
||||
if (menu.classList.contains('open')) this.closeMenu();
|
||||
else this.openMenu();
|
||||
this.setActive(!this.active);
|
||||
});
|
||||
menu.addEventListener('keydown', (event) => this.menuKey(event));
|
||||
document.addEventListener('click', (event) => {
|
||||
if (menu.classList.contains('open') && !wrap.contains(event.target as Node)) this.closeMenu();
|
||||
});
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Escape' && menu.classList.contains('open')) {
|
||||
this.closeMenu();
|
||||
button.focus();
|
||||
}
|
||||
});
|
||||
|
||||
wrap.append(button, menu);
|
||||
// Beside Export, outside the tools capsule, as its twin.
|
||||
const exportWrap = document.querySelector('.toolbar .export-wrap');
|
||||
if (exportWrap?.parentElement) exportWrap.parentElement.insertBefore(wrap, exportWrap);
|
||||
else document.querySelector('.toolbar')?.appendChild(wrap);
|
||||
return { button, menu };
|
||||
if (exportWrap?.parentElement) exportWrap.parentElement.insertBefore(button, exportWrap);
|
||||
else document.querySelector('.toolbar')?.appendChild(button);
|
||||
return button;
|
||||
}
|
||||
|
||||
private options(): HTMLButtonElement[] {
|
||||
return [...this.menu.querySelectorAll<HTMLButtonElement>('.archify-3d-option')].filter((o) => o.getAttribute('aria-disabled') !== 'true');
|
||||
}
|
||||
|
||||
private openMenu(): void {
|
||||
// Only one toolbar menu at a time: close Archify's own.
|
||||
document.querySelectorAll('.toolbar .preset-menu.open, .toolbar .export-menu.open').forEach((m) => {
|
||||
if (m !== this.menu) (m.previousElementSibling as HTMLElement | null)?.click();
|
||||
/**
|
||||
* 3D-only controls in Archify's bottom-right dock, where its own view
|
||||
* controls live: Stacked (toggle) and From above, before the zoom stepper;
|
||||
* the stepper's "100%" (a 2D zoom level) reads "Reset" while 3D is on.
|
||||
*/
|
||||
private buildNavControls(): { stack: HTMLButtonElement; top: HTMLButtonElement; reset: HTMLSpanElement } {
|
||||
const nav = this.container.querySelector<HTMLElement>('.diagram-nav');
|
||||
const make = (id: string, label: string, title: string, pressed: boolean, action: () => void) => {
|
||||
const button = document.createElement('button');
|
||||
button.id = id;
|
||||
button.type = 'button';
|
||||
button.title = title;
|
||||
button.setAttribute('aria-label', title);
|
||||
if (pressed) button.setAttribute('aria-pressed', 'false');
|
||||
button.textContent = label;
|
||||
button.hidden = true;
|
||||
button.addEventListener('click', (event) => {
|
||||
event.stopPropagation();
|
||||
action();
|
||||
});
|
||||
this.syncButtons();
|
||||
this.menu.classList.add('open');
|
||||
this.button.setAttribute('aria-expanded', 'true');
|
||||
const checked = this.options().find((o) => o.getAttribute('aria-checked') === 'true');
|
||||
(checked ?? this.options()[0])?.focus();
|
||||
}
|
||||
|
||||
private closeMenu(): void {
|
||||
this.menu.classList.remove('open');
|
||||
this.button.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
|
||||
private menuKey(event: KeyboardEvent): void {
|
||||
const options = this.options();
|
||||
const index = options.indexOf(document.activeElement as HTMLButtonElement);
|
||||
const move = (to: number) => {
|
||||
event.preventDefault();
|
||||
options[(to + options.length) % options.length]?.focus();
|
||||
return button;
|
||||
};
|
||||
if (event.key === 'ArrowDown') move(index + 1);
|
||||
else if (event.key === 'ArrowUp') move(index - 1);
|
||||
else if (event.key === 'Home') move(0);
|
||||
else if (event.key === 'End') move(options.length - 1);
|
||||
else if (event.key === 'Tab') this.closeMenu();
|
||||
}
|
||||
|
||||
private choose(value: MenuValue): void {
|
||||
switch (value) {
|
||||
case '2d':
|
||||
case '3d':
|
||||
if ((value === '3d') !== this.active) this.setActive(value === '3d');
|
||||
return;
|
||||
case 'flat':
|
||||
case 'stacked':
|
||||
this.stage?.setLayered(value === 'stacked');
|
||||
const stack = make('btn-3d-stack', 'STACK', 'Stacked: clients on top, data at the bottom', true, () => {
|
||||
this.stage?.setLayered(!this.stage.layered);
|
||||
this.syncButtons();
|
||||
this.save();
|
||||
return;
|
||||
case 'reset':
|
||||
this.stage?.fit();
|
||||
return;
|
||||
case 'top':
|
||||
this.stage?.top();
|
||||
return;
|
||||
});
|
||||
const top = make('btn-3d-top', 'TOP', 'From above: look straight down, like 2D', false, () => this.stage?.top());
|
||||
const reset = document.createElement('span');
|
||||
reset.className = 'archify-3d-reset';
|
||||
reset.textContent = 'RESET';
|
||||
const zoomOut = nav?.querySelector('[data-view="out"]') ?? null;
|
||||
nav?.insertBefore(stack, zoomOut);
|
||||
nav?.insertBefore(top, zoomOut);
|
||||
const resetButton = nav?.querySelector<HTMLButtonElement>('[data-view="reset"]');
|
||||
if (resetButton) {
|
||||
resetButton.appendChild(reset);
|
||||
resetButton.dataset.archify3dTitle = resetButton.title;
|
||||
}
|
||||
return { stack, top, reset };
|
||||
}
|
||||
|
||||
private addExportItem(): void {
|
||||
@@ -295,16 +196,17 @@ class Viewer3D {
|
||||
}
|
||||
|
||||
private syncButtons(): void {
|
||||
const layered = !!this.stage?.layered;
|
||||
this.button.toggleAttribute('data-active', this.active);
|
||||
for (const option of this.menu.querySelectorAll<HTMLButtonElement>('.archify-3d-option')) {
|
||||
const value = option.dataset.archify3d as MenuValue;
|
||||
if (value === '2d' || value === '3d') option.setAttribute('aria-checked', String((value === '3d') === this.active));
|
||||
if (value === 'flat' || value === 'stacked') option.setAttribute('aria-checked', String((value === 'stacked') === layered));
|
||||
// Levels and camera only apply to the 3D view.
|
||||
if (value !== '2d' && value !== '3d') option.setAttribute('aria-disabled', String(!this.active));
|
||||
}
|
||||
if (this.exportItem) this.exportItem.hidden = !this.active;
|
||||
const on = this.active;
|
||||
this.toggle.setAttribute('aria-checked', String(on));
|
||||
this.toggle.setAttribute('aria-label', on ? '3D view on; switch to 2D' : '2D view; switch to 3D');
|
||||
this.toggle.title = on ? 'Switch to 2D' : 'Switch to 3D';
|
||||
for (const seg of this.toggle.querySelectorAll<HTMLElement>('.archify-3d-seg')) seg.classList.toggle('on', (seg.dataset.seg === '3d') === on);
|
||||
this.stackButton.hidden = !on;
|
||||
this.stackButton.setAttribute('aria-pressed', String(!!this.stage?.layered));
|
||||
this.topButton.hidden = !on;
|
||||
const resetButton = this.resetLabel.parentElement as HTMLButtonElement | null;
|
||||
if (resetButton) resetButton.title = on ? 'Reset view: show the whole diagram (0)' : resetButton.dataset.archify3dTitle ?? resetButton.title;
|
||||
if (this.exportItem) this.exportItem.hidden = !on;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- mode
|
||||
|
||||
+26
-30
@@ -10,36 +10,32 @@ html.archify-3d-on .diagram-container { min-height: max(420px, 62vh); }
|
||||
.archify-3d-stage canvas:active { cursor: grabbing; }
|
||||
html.archify-3d-on .diagram-container > :not(svg):not(.archify-3d-stage) { z-index: 2; }
|
||||
|
||||
/* The 3D button is Export's twin: same pill, same type, same menu. */
|
||||
.toolbar #btn-3d { padding-right: 0.95rem; padding-left: 1rem; color: var(--bg); font-weight: 550; }
|
||||
.toolbar #btn-3d::before { inset: 0.25rem 0; border-radius: 0.6875rem; background: var(--text);
|
||||
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.12), 0 6px 16px -8px rgba(15, 23, 42, 0.4); }
|
||||
.toolbar #btn-3d:hover,
|
||||
.toolbar #btn-3d[aria-expanded="true"] { color: var(--bg); }
|
||||
.toolbar #btn-3d:hover::before,
|
||||
.toolbar #btn-3d[aria-expanded="true"]::before { background: color-mix(in srgb, var(--text) 86%, var(--bg)); }
|
||||
/* While the 3D view is on, the pill wears a cyan ring. */
|
||||
.toolbar #btn-3d[data-active]::before { box-shadow: 0 0 0 2px var(--frontend-stroke), 0 6px 16px -8px rgba(15, 23, 42, 0.4); }
|
||||
.toolbar .archify-3d-wrap .archify-3d-menu { right: 0; }
|
||||
/* 2D | 3D switch: Export's twin. Both states visible, the filled half is current. */
|
||||
.toolbar #btn-3d { padding: 0 0.25rem; gap: 0; color: var(--text-muted); }
|
||||
.toolbar #btn-3d::before { inset: 0.25rem 0; border-radius: 0.6875rem; background: color-mix(in srgb, var(--panel) 78%, transparent);
|
||||
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--panel-border) 85%, transparent), 0 1px 2px rgba(15, 23, 42, 0.04); backdrop-filter: blur(12px); }
|
||||
.toolbar #btn-3d:hover::before { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--text) 22%, transparent), 0 1px 2px rgba(15, 23, 42, 0.04); }
|
||||
.toolbar #btn-3d[aria-checked] { color: var(--text-muted); }
|
||||
.archify-3d-seg { display: inline-flex; align-items: center; height: 1.75rem; padding: 0 0.7rem; border-radius: 0.5rem; font-weight: 550; transition: background 0.15s, color 0.15s; }
|
||||
.archify-3d-seg.on { color: var(--bg); background: var(--text); box-shadow: 0 1px 2px rgba(15, 23, 42, 0.12), 0 6px 16px -8px rgba(15, 23, 42, 0.4); }
|
||||
.toolbar #btn-3d:hover .archify-3d-seg:not(.on) { color: var(--text); }
|
||||
|
||||
/* 3D-only controls in the bottom-right dock, in Archify's mode-button style. */
|
||||
.diagram-nav #btn-3d-stack,
|
||||
.diagram-nav #btn-3d-top { display: inline-flex; align-items: center; gap: 0.375rem; min-width: auto; padding-inline: 0.55rem 0.65rem; color: var(--text-muted); font-weight: 500; }
|
||||
.diagram-nav #btn-3d-stack[hidden],
|
||||
.diagram-nav #btn-3d-top[hidden] { display: none; }
|
||||
.diagram-nav #btn-3d-stack { position: relative; margin-left: 0.375rem; }
|
||||
.diagram-nav #btn-3d-stack::after { position: absolute; top: 0.55rem; bottom: 0.55rem; left: -0.19rem; width: 1px; background: color-mix(in srgb, var(--panel-border) 90%, transparent); content: ""; pointer-events: none; }
|
||||
.diagram-nav #btn-3d-stack::before,
|
||||
.diagram-nav #btn-3d-top::before { flex: none; width: 0.875rem; height: 0.875rem; background: currentColor; content: ""; opacity: 0.85; -webkit-mask: var(--nav-glyph) center / contain no-repeat; mask: var(--nav-glyph) center / contain no-repeat; }
|
||||
.diagram-nav #btn-3d-stack { --nav-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6' stroke-linejoin='round'%3E%3Cpath d='M10 2.5 17.5 6.3 10 10 2.5 6.3z'/%3E%3Cpath d='m2.5 10 7.5 3.8 7.5-3.8M2.5 13.7 10 17.5l7.5-3.8'/%3E%3C/g%3E%3C/svg%3E"); }
|
||||
.diagram-nav #btn-3d-top { --nav-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 2.5v9M6.5 8l3.5 3.5L13.5 8'/%3E%3Cpath d='M3 14.5h14M5 17.5h10'/%3E%3C/g%3E%3C/svg%3E"); }
|
||||
.diagram-nav #btn-3d-stack[aria-pressed="true"] { color: var(--text); background: color-mix(in srgb, var(--frontend-stroke) 14%, transparent); }
|
||||
/* The stepper's middle button shows the 2D zoom level; in 3D it is the reset. */
|
||||
.archify-3d-reset { display: none; color: var(--text-muted); font-size: 0.75rem; font-weight: 500; }
|
||||
html.archify-3d-on .diagram-nav [data-view="reset"] > :not(.archify-3d-reset) { display: none !important; }
|
||||
html.archify-3d-on .archify-3d-reset { display: inline; }
|
||||
.toolbar .export-menu button[data-archify3d-export]::before { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 2.5l7 4v7l-7 4-7-4v-7z'/%3E%3Cpath d='M3 6.5l7 4 7-4M10 10.5v7'/%3E%3C/g%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 2.5l7 4v7l-7 4-7-4v-7z'/%3E%3Cpath d='M3 6.5l7 4 7-4M10 10.5v7'/%3E%3C/g%3E%3C/svg%3E"); }
|
||||
|
||||
/* Menu rows: Archify's .preset-option layout, with an icon swatch. */
|
||||
.toolbar .archify-3d-menu { width: 18.5rem; }
|
||||
.toolbar .archify-3d-menu .preset-menu-heading { padding-top: 0.6rem; }
|
||||
.toolbar .archify-3d-menu .preset-menu-heading:first-child { padding-top: 0.45rem; }
|
||||
.archify-3d-swatch { display: grid; place-items: center; width: 2.375rem; height: 1.75rem; border-radius: 0.4375rem; color: var(--text-muted);
|
||||
background: color-mix(in srgb, var(--text) 5%, transparent); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--text) 14%, transparent);
|
||||
transition: box-shadow 0.14s ease, color 0.14s ease; }
|
||||
.toolbar .archify-3d-option[aria-checked="true"] .archify-3d-swatch { color: var(--frontend-stroke); box-shadow: 0 0 0 2px var(--toolbar-menu-bg), 0 0 0 3.5px var(--frontend-stroke); }
|
||||
.archify-3d-swatch-icon { width: 1.05rem; height: 1.05rem; background: currentColor; -webkit-mask: center / contain no-repeat; mask: center / contain no-repeat; }
|
||||
.archify-3d-i-2d { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='14' height='12' rx='1.5'/%3E%3Cpath d='M6 8h3M6 11h5M12.5 8h1.5'/%3E%3C/g%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='14' height='12' rx='1.5'/%3E%3Cpath d='M6 8h3M6 11h5M12.5 8h1.5'/%3E%3C/g%3E%3C/svg%3E"); }
|
||||
.archify-3d-i-3d { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 2.5l7 4v7l-7 4-7-4v-7z'/%3E%3Cpath d='M3 6.5l7 4 7-4M10 10.5v7'/%3E%3C/g%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 2.5l7 4v7l-7 4-7-4v-7z'/%3E%3Cpath d='M3 6.5l7 4 7-4M10 10.5v7'/%3E%3C/g%3E%3C/svg%3E"); }
|
||||
.archify-3d-i-flat { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 6 17.5 9.8 10 13.6 2.5 9.8z'/%3E%3C/g%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 6 17.5 9.8 10 13.6 2.5 9.8z'/%3E%3C/g%3E%3C/svg%3E"); }
|
||||
.archify-3d-i-stacked { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 2.5 17.5 6.3 10 10 2.5 6.3z'/%3E%3Cpath d='m2.5 10 7.5 3.8 7.5-3.8M2.5 13.7 10 17.5l7.5-3.8'/%3E%3C/g%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 2.5 17.5 6.3 10 10 2.5 6.3z'/%3E%3Cpath d='m2.5 10 7.5 3.8 7.5-3.8M2.5 13.7 10 17.5l7.5-3.8'/%3E%3C/g%3E%3C/svg%3E"); }
|
||||
.archify-3d-i-reset { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 10a6.5 6.5 0 1 0 2-4.7'/%3E%3Cpath d='M3.5 3.5v3.5H7'/%3E%3C/g%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 10a6.5 6.5 0 1 0 2-4.7'/%3E%3Cpath d='M3.5 3.5v3.5H7'/%3E%3C/g%3E%3C/svg%3E"); }
|
||||
.archify-3d-i-top { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 2.5v9M6.5 8l3.5 3.5L13.5 8'/%3E%3Cpath d='M3 14.5h14M5 17.5h10'/%3E%3C/g%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 2.5v9M6.5 8l3.5 3.5L13.5 8'/%3E%3Cpath d='M3 14.5h14M5 17.5h10'/%3E%3C/g%3E%3C/svg%3E"); }
|
||||
.toolbar .archify-3d-option[aria-disabled="true"] { opacity: 0.42; cursor: default; }
|
||||
.toolbar .archify-3d-option[aria-disabled="true"]:hover { background: transparent; }
|
||||
.archify-3d-kbd { justify-self: end; min-width: 1rem; color: var(--text-faint); font: inherit; font-size: 0.6875rem; text-align: center; }
|
||||
.archify-3d-kbd:empty { visibility: hidden; }
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user