Compare commits

..

316 Commits
soccer ... main

Author SHA1 Message Date
add1ece149 Added new relic license key to env.production
All checks were successful
Build and Deploy / build (push) Successful in 1m41s
2026-01-13 17:29:51 -06:00
421cd97fe9 Add console log forwarding to New Relic and enable application logging
All checks were successful
Build and Deploy / build (push) Successful in 1m47s
- Add console shim to forward log/error/warn/info to New Relic
- Enable application_logging with forwarding in newrelic.cjs
- Import newrelic in server.js for recordLogEvent API
- Update CI workflow with New Relic config

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 17:21:29 -06:00
58959fe347 Fix newrelic config to use .cjs extension for CommonJS
Rename newrelic.js to newrelic.cjs to work with ES module projects

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 15:18:20 -06:00
d9cd0692b5 Add New Relic Node.js APM monitoring to backend
- Install newrelic package for server-side APM
- Create newrelic.js configuration with distributed tracing enabled
- Update npm scripts to preload agent via -r flag for ES modules
- Correlates with existing browser agent for end-to-end tracing

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 15:15:27 -06:00
3155cc930f Fix camera positioning, label z-fighting, and remove dead code
- Fix desktop camera to be directly above platform by resetting local position
- Increase label back offset from 0.001 to 0.005 to prevent z-fighting
- Use refreshBoundingInfo({}) for consistency with codebase
- Remove unused copyToPublic from pouchData.ts
- Remove dead DiagramEntityAdapter and integration/gizmo module
- Remove unused netlify functions and integration utilities
- Clean up unused imports and commented code across multiple files

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 15:06:18 -06:00
8c2b7f9c7d Fix diagram text sync, resize handle positioning, and PouchDB delete handling
- Fix diagramObject text setter to update entity before notifying observers
- Improve ResizeGizmo handle positioning directly at corners/faces with constant screen-size scaling
- Fix PouchDB sync to handle deleted documents using _id field for compatibility

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 11:16:43 -06:00
960c64984e Add New Relic browser agent for monitoring
Some checks failed
Build and Deploy / build (push) Failing after 6m34s
- Add @newrelic/browser-agent dependency
- Initialize browser agent in webApp.ts with distributed tracing enabled

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 07:15:18 -06:00
d79f4efa98 Add Auth0 environment variables to build step
Some checks failed
Build and Deploy / build (push) Failing after 6m31s
VITE_AUTH0_CLIENTID and VITE_AUTH0_DOMAIN are needed at build time
as they get embedded into the frontend bundle by Vite.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 06:42:40 -06:00
8bfe7bb174 Add Cloudflare environment variables to CI/CD pipeline
All checks were successful
Build and Deploy / build (push) Successful in 1m34s
- Update build.yml to create .env.production from Gitea secrets
  - ANTHROPIC_API_KEY, CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_API_TOKEN
  - Secure file with chmod 600 (owner read only)
  - Preserve env file across deployments

- Update start.sh to source .env.production if it exists
  - Parse and export variables before starting server
  - Skip comments and empty lines

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 06:40:33 -06:00
03217f3e65 Add Cloudflare Workers AI provider and multiple AI chat improvements
- Add Cloudflare Workers AI as third provider alongside Claude and Ollama
  - New cloudflare.js API handler with format conversion
  - Tool converter functions for Cloudflare's OpenAI-compatible format
  - Handle [TOOL_CALLS] and [Called tool:] text formats from Mistral
  - Robust parser that handles truncated JSON responses

- Add usage tracking with cost display
  - New usageTracker.js service for tracking token usage per session
  - UsageDetailModal component showing per-request breakdown
  - Cost display in ChatPanel header

- Add new diagram manipulation features
  - Entity scale and rotation support via modify_entity tool
  - Wikipedia search tool for researching topics before diagramming
  - Clear conversation tool to reset chat history
  - JSON import from hamburger menu (moved from ChatPanel)

- Fix connection label rotation in billboard mode
  - Labels no longer have conflicting local rotation when billboard enabled
  - Update rotation when rendering mode changes

- Improve tool calling reliability
  - Add MAX_TOOL_ITERATIONS safety limit
  - Break loop after model switch to prevent context issues
  - Increase max_tokens to 4096 to prevent truncation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 06:31:43 -06:00
fd81ba3be7 update group permissions in deploy.
All checks were successful
Build and Deploy / build (push) Successful in 1m38s
2025-12-30 10:43:26 -06:00
c58ce483dd update group permissions in deploy.
Some checks failed
Build and Deploy / build (push) Failing after 1m35s
2025-12-30 10:28:15 -06:00
13ecd5a626 Add quick console test.
Some checks failed
Build and Deploy / build (push) Failing after 1m33s
2025-12-30 10:22:01 -06:00
82807dcfce Fix deployment to preserve data dir in place
All checks were successful
Build and Deploy / build (push) Successful in 1m35s
Use find to delete all files except data directory instead of
moving to /tmp which fails due to sticky bit permissions.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 09:48:35 -06:00
739775ea94 Fix deployment to work within /opt/immersive directory
Some checks failed
Build and Deploy / build (push) Failing after 1m29s
Move contents instead of directory itself to avoid permission issues
with gitea-runner user who has write access inside but not to /opt.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 09:40:32 -06:00
e2216c17e8 Add Alpine Linux service setup and CI/CD deployment
Some checks failed
Build and Deploy / build (push) Failing after 1m44s
Node.js CI / build (push) Has been cancelled
- Add ALPINE_SERVICE.md with full setup instructions
- Add start.sh script for OpenRC service
- Update build.yml for deployment to /opt/immersive
- Configure proper permissions for immersive user
- Add Gitea runner setup instructions with sudo config
- Add .env.production to gitignore

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 09:21:08 -06:00
33019c116b change server start.
Some checks are pending
Node.js CI / build (push) Waiting to run
Build / build (push) Successful in 2m19s
2025-12-30 07:11:36 -06:00
bd833e236a quick test of gitea runner config.
Some checks are pending
Build / build (push) Waiting to run
Node.js CI / build (push) Waiting to run
2025-12-30 07:00:35 -06:00
0916829ba2 quick test of gitea runner config.
Some checks failed
Build / build (push) Has been cancelled
Node.js CI / build (push) Has been cancelled
2025-12-30 06:58:43 -06:00
122c0d2ab0 quick test of gitea runner config.
Some checks are pending
Build / build (push) Waiting to run
Node.js CI / build (push) Waiting to run
2025-12-29 20:33:04 -06:00
1e174e81d3 Add local database mode for browser-only diagrams
Some checks failed
Node.js CI / build (push) Waiting to run
Build / build (push) Failing after 15m8s
- Add /db/local/:db path type that stores diagrams locally without syncing
- New diagrams now default to local storage (browser-only)
- Share button creates public copy when sharing local diagrams
- Add storage type badges (Local/Public/Private) in diagram manager
- Add GitHub Actions workflow for automated builds
- Block local- database requests at server with 404

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 18:13:43 -06:00
a772372b2b Add public URL sharing with express-pouchdb sync
- Add express-pouchdb for self-hosted PouchDB sync server
- Public databases (/db/public/:db) accessible without auth
- Add auth middleware for public/private database access
- Simplify share button to copy current URL to clipboard
- Move feature config from static JSON to dynamic API endpoint
- Add PouchDB sync to PouchData class for real-time collaboration
- Fix Express 5 compatibility by patching req.query
- Skip express.json() for /pouchdb routes (stream handling)
- Remove unused PouchdbPersistenceManager and old share system

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 11:49:56 -06:00
74a2d179b9 Add Ollama as alternative AI provider and modify_connection tool
Ollama Integration:
- Add providerConfig.js for managing AI provider settings
- Add toolConverter.js to convert between Claude and Ollama formats
- Add ollama.js API handler with function calling support
- Update diagramAI.ts with Ollama models (llama3.1, mistral, qwen2.5)
- Route requests to appropriate provider based on selected model
- Use 127.0.0.1 to avoid IPv6 resolution issues

New modify_connection Tool:
- Add modify_connection tool to change connection labels and colors
- Support finding connections by label or by from/to entities
- Add chatModifyConnection event handler in diagramManager
- Clarify in tool descriptions that empty string removes labels

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-27 12:08:17 -06:00
5891dfd6b7 Fix hamburger menu visibility and feature config format
- Fix z-index layering so hamburger menu appears above canvas
  - Lower canvas zIndex from 1000 to 1
  - Add zIndex={100} to Affix and Menu components
  - Add position="bottom-start" to prevent dropdown going off-screen

- Update feature configs to use string states instead of booleans
  - Convert all JSON configs from true/false to "on"/"off"/"coming-soon"/"pro"
  - Fix BASIC_FEATURE_CONFIG to enable core features for logged-in users
  - This fixes menu items not responding to clicks when authenticated

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-21 12:00:37 -06:00
a7aa385d98 Add model selection and ground-projected directional placement
Model management:
- Add list_models, get_current_model, set_model tools
- Support Claude Sonnet 4, Opus 4, and Haiku 3.5
- Model selection persists for session duration

Directional placement improvements:
- Compute ground-projected forward/right vectors from camera
- Accounts for camera being parented to moving platform
- "Forward" means forward on ground plane, ignoring vertical look angle
- Pre-calculate example positions for left/right/forward/back
- Update system prompt to use get_camera_position for relative directions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-20 16:14:44 -06:00
c9dc61b918 Add camera position tool and fix entity modification bugs
- Add get_camera_position tool for positioning entities relative to user view
- Fix color change causing entities to disappear (dispose mesh before rebuild)
- Fix connections being lost when modifying entities (defer disposal, let
  scene observer re-find meshes after they're recreated with same ID)
- Add position and color setters to DiagramObject for real-time updates
- Add debug logging to diagramAI and claude.js for troubleshooting

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-20 15:59:30 -06:00
2fd87b2d14 Fix entity connections and add clear diagram tool
Connection fixes:
- Add chatResolveEntity event to resolve labels to entity IDs
- Update connectEntities to resolve from/to labels before creating connection
- Auto-generate connection labels as "{from label} to {to label}"

Clear diagram tool:
- Add clear_diagram tool with confirmation requirement
- Claude prompts user for confirmation before executing
- Clears all entities from diagram and resets session
- Syncs empty entity list to server after clearing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-20 14:02:11 -06:00
7769910027 Add server-side session persistence for chat
Implement session management to maintain conversation history and entity
context across page refreshes. Sessions are stored in-memory and include:
- Conversation history (stored server-side, restored on reconnect)
- Entity snapshots synced before each message for LLM context
- Auto-injection of diagram state into Claude's system prompt

Key changes:
- Add session store service with create/resume/sync/clear operations
- Add session API endpoints (/api/session/*)
- Update Claude API to inject entity context and manage history
- Update ChatPanel to initialize sessions and sync entities
- Add debug endpoint for inspecting session state

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-20 13:42:01 -06:00
1152ab0d0c Add Express API server for Claude API proxy
- Add Express server with vite-express for combined frontend/API serving
- Create modular API route structure (server/api/)
- Implement Claude API proxy with proper header injection
- Support split deployment via API_ONLY and ALLOWED_ORIGINS env vars
- Remove Claude proxy from Vite config (now handled by Express)
- Add migration plan documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-20 12:32:49 -06:00
1ccdab2780 Added chat interface. 2025-12-20 11:25:18 -06:00
e714c3d3df Added chat interface. 2025-12-20 11:25:14 -06:00
54e5017c38 Added chat interface. 2025-12-20 11:24:31 -06:00
8a78e45440 version bump 2025-12-19 15:32:28 -06:00
1c50dd5c84 Implement three-state feature flag system with upgrade badges
Feature States:
- 'on': Feature fully accessible
- 'off': Feature hidden from menus
- 'coming-soon': Visible with "Coming Soon!" badge, not clickable
- 'basic': Visible with "Sign Up for Free" badge, triggers Auth0 login
- 'pro': Visible with "Upgrade to Pro" badge (for future upgrade flow)

Changes:
- Update FeatureState type to support 5 states (on/off/coming-soon/basic/pro)
- Consolidate GUEST_FEATURE_CONFIG as DEFAULT_FEATURE_CONFIG
- Create ComingSoonBadge component for coming-soon features
- Create UpgradeBadge component for basic/pro tier requirements
- Update VR Experience hamburger menu to maintain open/closed state
- Make menu default to open, persist state in localStorage
- Make 'basic' features clickable to trigger Auth0 sign-in
- Update createDiagramModal to show appropriate badges
- Fix camera initial position to match VR rig (prevent flip on load)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-21 14:33:11 -06:00
31dd8a89da Clean up code formatting and remove unused functions
- Remove unused createGrassGround function from customEnvironment
- Remove commented HemisphericLight code
- Fix deviceDetection to use proper VR detection instead of hardcoded true
- Clean up whitespace in spinner.ts and animatedLineTexture.ts
- Pass empty object to refreshBoundingInfo to match API signature

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-20 13:23:17 -06:00
1098f03c7d Optimize STL asset loading with promise-based caching
- Replace ImportMeshAsync with LoadAssetContainerAsync for person.stl
- Cache loading promise to prevent race conditions and multiple fetches
- Use instantiateModelsToScene() to create mesh instances from cached container
- Simplify buildMesh signature to use DefaultScene singleton
- Add Havok physics WASM prefetch hint to index.html

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-20 13:19:24 -06:00
0e053bf69c Add Quest VR onboarding flow with auto-entry prompt
Implemented comprehensive VR onboarding experience for Quest users:

- Add demo database template with pre-built architecture diagram
- Implement automatic demo template loading on first visit
- Create VREntryPrompt component for seamless VR mode entry
- Add device detection utilities for Quest/VR headset identification
- Integrate export/import functionality for diagram templates
- Add About page with device-aware CTA and VR benefits
- Remove legacy tutorial and FirstVisitVr modal for demo flow
- Add upgrade prompts and tiered feature configuration

Quest users now see prominent VR entry prompt when navigating to /db/** paths,
providing one-tap entry into immersive mode after scene initialization.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-20 10:47:28 -06:00
0b81605bdf Fix label positioning to use world space bounding boxes
Labels were incorrectly mixing local and global transform matrices, causing
incorrect positioning on scaled/rotated meshes. Now properly converts world
space bounding box positions to mesh local space using temporary TransformNode.

Changes:
- updateTextNode.ts: Use boundingBox.maximumWorld instead of boundingSphere.maximum
- diagramObject.ts: Add empty object param to refreshBoundingInfo()
- inputTextView.ts: Adjust input handle default position and mesh offsets

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 16:52:04 -06:00
7849bf4eb2 Extract toolbox buttons into standalone reusable classes
Refactor Toolbox button code into separate, focused classes following Single Responsibility Principle.

New Button Classes:
- ExitXRButton (src/objects/buttons/ExitXRButton.ts)
  * Encapsulates exit XR functionality
  * Takes XR experience, scene, parent, and optional position
  * Handles click events to exit XR session
  * Provides dispose() and transform getter

- ConfigButton (src/objects/buttons/ConfigButton.ts)
  * Encapsulates config panel toggle functionality
  * Uses dependency injection for toggle callback
  * Configurable position relative to parent
  * Provides dispose() and transform getter

- RenderModeButton (src/objects/buttons/RenderModeButton.ts)
  * Encapsulates render mode cycling functionality
  * Internally manages render mode state
  * Automatically recreates button with new label on mode change
  * Cycles through all available rendering modes
  * Provides dispose() and transform getter

Toolbox Changes:
- Removed createRenderModeButton() and updateRenderModeButton() methods
- Simplified setupXRButton() to instantiate button classes
- Reduced button-related code by ~120 lines
- Added button instance properties for cleanup
- Clean, declarative button creation with clear positioning

Benefits:
- Single Responsibility Principle - each button class has one purpose
- Reusability - buttons can be used anywhere in the app
- Testability - each button can be tested independently
- Cleaner code - Toolbox class focuses on core tool management
- Better dependencies - clear interfaces and dependency injection
- Easier maintenance - button logic isolated and self-contained

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 16:14:01 -06:00
e329b95f2f Fix AppConfig persistence and consolidate handle storage
AppConfig Persistence Fixes:
- Fix constructor to properly handle null localStorage values
- Add null check before JSON.parse to prevent errors
- Create fresh config copies with spread operator to avoid reference issues
- Add better error handling and logging for config loading
- Initialize handles array properly

React ConfigModal Improvements:
- Fix config initialization to get fresh values on render instead of stale module-level values
- Separate useEffect hooks for each config property (prevents unnecessary updates)
- Fix SegmentedControl string-to-number conversion (locationSnaps now use "0.01", "0.1" format)
- Enable/disable logic now properly sets values to 0 when disabled

Handle Storage Consolidation:
- Create dynamic HandleConfig type with Vec3 for serializable position/rotation/scale
- Add handles array to AppConfigType for flexible handle storage
- Replace individual localStorage keys with centralized AppConfig storage
- Add handle management methods: getHandleConfig, setHandleConfig, removeHandleConfig, getAllHandleConfigs
- Update Handle class to read from AppConfig instead of direct localStorage
- Update dropMesh to save handles via AppConfig using Vec3 serialization
- Convert between BabylonJS Vector3 and serializable Vec3 at conversion points

Benefits:
- Single source of truth for all configuration
- Proper localStorage persistence across page reloads
- Dynamic handle creation without code changes
- Type-safe configuration with proper JSON serialization
- Consolidated storage (one appConfig key instead of multiple handle-* keys)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 15:34:40 -06:00
25963d5289 version bump 2025-11-19 13:35:40 -06:00
aa0810be02 Refactor Handle class and fix VR positioning
Major improvements to Handle class architecture:
- Replace positional constructor parameters with options object pattern (HandleOptions interface)
- Add automatic platform parenting - handles now find and parent themselves to platform
- Rename idStored → hasStoredPosition for better clarity
- Remove unused staort() method
- Improve position/rotation persistence with better error handling
- Add comprehensive JSDoc documentation
- Use .parent instead of setParent() for proper local space coordinates

Update all Handle usage sites:
- Toolbox: Use new API with position (-.5, 1.5, .5) and zero rotation
- InputTextView: Use new API with position (0, 1.5, .5) and zero rotation
- VRConfigPanel: Use new API with position (.5, 1.5, .5) and zero rotation
- Remove manual platform parenting logic (61 lines of duplicated code removed)
- Remove local position offsets that were overriding handle positions

Fix VR entry positioning:
- Disable camera-relative positioning in groundMeshObserver
- Handles now use their configured defaults or saved localStorage positions
- Positions are now in platform local space as intended

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 13:28:48 -06:00
15c6617151 Fix VR config panel positioning to prevent overflow below handle
The VR config panel was extending 55cm below the handle bar due to
incorrect positioning calculation.

Problem:
- Panel dimensions: 2m wide × 1.5m tall
- Panel was positioned at y=0.2m above handle center
- This placed panel bottom at y=-0.55m (55cm BELOW handle)
- Panel overflowed significantly below the handle bar

Solution:
- Calculate proper position based on panel height
- Position panel center at y=0.8m (0.75m half-height + 0.05m gap)
- Panel bottom now sits 5cm above handle, matching toolbox appearance
- Add 0.6x scaling to match toolbox compact size (1.2m×0.9m actual)

Result:
- Panel bottom aligns just above handle bar
- Consistent visual relationship with toolbox
- Comfortable viewing distance and ergonomics in VR

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 09:46:51 -06:00
adc80c54c4 Optimize animated connection textures and fix material texture bleeding
Performance Optimizations (~90% improvement):
- Implement texture color caching in AnimatedLineTexture
  - Reuse textures for connections with same color
  - Reduces texture count by 70-90% with duplicate colors
- Reduce animation update frequency from every frame to every other frame
  - Halves CPU-to-GPU texture updates while maintaining smooth animation
- Add texture preloading for all 16 toolbox colors
  - Eliminates first-connection creation stutter
- Add GetCacheStats, ClearCache, and PreloadTextures utility methods

Bug Fixes:
1. Fix texture disappearing when moving objects with connections
   - Root cause: Shared cached textures were disposed on connection update
   - Solution: Never dispose cached textures, only swap references
   - Add safety check in DisposeTexture to prevent cached texture disposal

2. Fix UI text textures bleeding to normal materials
   - Add metadata.isUI = true to 10+ UI components:
     - Button.ts (with unique material names per button)
     - handle.ts, roundButton.ts, createLabel.ts, updateTextNode.ts
     - spinner.ts, vrConfigPanel.ts, buildImage, introduction.ts
     - ResizeGizmo.ts
   - Update LightmapGenerator filter to check metadata.isUI first
   - Change exact name match to startsWith for button materials

3. Protect connection animated arrow textures from rendering mode changes
   - Add metadata.isConnection = true to connection materials
   - Update LightmapGenerator to skip connection materials
   - Connections maintain animated arrows in all rendering modes

Technical Details:
- Texture caching follows existing LightmapGenerator pattern
- All UI materials now consistently marked with metadata flags
- Rendering mode filter uses metadata-first approach with fallback checks
- Connection materials preserve textures via metadata.preserveTextures flag

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 16:37:22 -06:00
0e318e7cc7 Fix Fly Mode and Snap Turn not applying at runtime in VR
Add Observable subscription to Rigplatform so Fly Mode and Snap Turn
settings take effect immediately when changed in the VR config panel,
instead of only applying after exiting and re-entering XR.

Changes to src/controllers/rigplatform.ts:
- Import Observer, appConfigInstance, and AppConfigType
- Add _configObserver property to track subscription
- Add _subscribeToConfigChanges() method in constructor
- Subscribe to onConfigChangedObservable to update flyMode and turnSnap
- Add dispose() method to clean up observer and controllers
- Log config changes for debugging

Changes to src/menus/vrConfigPanel.ts:
- Remove unused index parameter in forEach loop

Root cause: Settings were only applied once at Rigplatform initialization
in groundMeshObserver.ts. Config changes during VR session were saving to
localStorage but not updating the running Rigplatform instance.

Result: Fly Mode and Snap Turn now update in real-time when changed in VR.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 15:27:08 -06:00
5091ca0bab Implement Phase 7: Add Label Rendering Mode controls to VR config panel
Complete the final phase of VR configuration panel implementation by adding
Label Rendering Mode selection controls. This allows users to configure how
diagram labels are rendered in the VR environment.

Changes:
- Add radio-style buttons for 4 label rendering modes:
  - Fixed: Static orientation
  - Billboard: Always faces camera (default)
  - Dynamic: Coming soon (disabled)
  - Distance-based: Coming soon (disabled)
- Implement disabled styling for future modes (gray background, 50% opacity)
- Wire up to appConfigInstance.setLabelRenderingMode()
- Update UI when config changes from external sources (2D modal)
- Add updateLabelModeButtonStates() for visual state management

This completes all 7 phases of VRCONFIGPLAN.md implementation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 15:05:13 -06:00
3002181160 Implement Phase 6: Snap Turn controls for VR config panel
Add fully functional Snap Turn controls:
- Toggle button (Enabled/Disabled) with blue/gray color coding
- 5 angle buttons: 22.5°, 45°, 90°, 180°, 360°
- Selected button highlighted in blue with bold text
- Disabled appearance when snap is off (50% opacity)
- Wire up to appConfigInstance.setTurnSnap()
- Update UI from config observable changes

Follows same pattern as Rotation Snap section but for snap turning.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 13:44:53 -06:00
f8ae71a962 Implement Phase 5: Fly Mode toggle for VR config panel
Add Fly Mode toggle control:
- Single toggle button showing "Fly Mode Enabled" or "Fly Mode Disabled"
- 400px wide button with blue/gray color coding
- Wire up to appConfigInstance.setFlyMode()
- Update UI from config observable changes

Simplest section - just one boolean toggle, no value selection needed.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 13:36:19 -06:00
c66da87401 Migrate from legacy config to new AppConfig singleton system
Remove dual config system and migrate all code to use appConfigInstance.

**Phase 1: Update VR Controller Code**
- snapAll.ts: Replace getAppConfig() with appConfigInstance.current
  - Use `rotateSnap > 0` instead of rotationSnapEnabled flag
  - Use `locationSnap > 0` instead of locationSnapEnabled flag
  - Remove parseFloat() calls (values already numbers)
- groundMeshObserver.ts: Direct property replacement
  - flyModeEnabled → flyMode
  - snapTurnSnap → turnSnap (remove parseFloat)
- customPhysics.ts: Add enabled checks and update
  - Add `> 0` checks (was applying unconditionally)
  - Use locationSnap and rotateSnap directly

**Phase 2: Remove Legacy Config Bridge**
- vrConfigPanel.ts: Remove syncLegacyConfig() method and all calls
- configModal.tsx: Remove legacy localStorage 'config' writes

**Phase 3: Cleanup**
- appConfig.ts: Remove legacy code (ConfigType, getAppConfig(), setAppConfig())
- Remove unused log import

**Benefits:**
- Eliminates dual config system confusion
- Fixes precision error from string "0" values
- Single source of truth via appConfigInstance
- Reactive updates via Observable pattern
- Cleaner, simpler codebase

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 13:20:18 -06:00
3cf3d996dc Implement Phase 4 and fix config sync for actual snap functionality
Phase 4: Add Rotation Snap controls
- Toggle button (Enabled/Disabled) with blue/gray color coding
- 5 rotation value buttons: 22.5°, 45°, 90°, 180°, 360°
- Selected button highlighted in blue with bold text
- Disabled appearance when snap is off (50% opacity)
- Wire up to appConfigInstance.setRotateSnap()
- Update UI from config observable changes

Critical fix: Sync to legacy config system
- Add syncLegacyConfig() method to write to localStorage 'config' key
- Call after all snap value changes (location and rotation)
- Legacy config is used by snapAll.ts for actual object snapping
- Ensures VR config changes affect real VR object manipulation
- Matches ConfigModal pattern for backward compatibility

Without this sync, changes in VR panel had no effect on actual snapping behavior.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 12:53:41 -06:00
5889a1ed79 Implement Phase 3: Location Snap controls for VR config panel
Add fully functional Location Snap controls:
- Toggle button (Enabled/Disabled) with blue/gray color coding
- 5 snap value buttons: 1cm, 5cm, 10cm, 50cm, 1m
- Selected button highlighted in blue with bold text
- Disabled appearance when snap is off (50% opacity)
- Wire up to appConfigInstance.setGridSnap()
- Update UI from config observable changes

Fix layout issues:
- Change texture aspect ratio from 2048x2048 to 2048x1536 (4:3) to match plane dimensions
- Add adaptHeightToChildren to section containers for proper auto-sizing
- Add horizontal alignment to button containers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 12:13:22 -06:00
be311e6dc8 Implement Phase 2 UI layout and toolbox integration for VR config panel
Phase 2: Add UI layout structure to VRConfigPanel
- Create 5 section containers with titles (Location Snap, Rotation Snap, Fly Mode, Snap Turn, Label Rendering Mode)
- Add visual separators between sections using Rectangle components
- Style with proper padding and spacing for VR readability (60px titles, blue #4A9EFF)
- Store section content containers as private properties for Phase 3-7 controls

Toolbox Integration (Phase 8 partial):
- Instantiate VRConfigPanel in DiagramMenuManager constructor
- Add "Config" button to toolbox (bottom-left, opposite Exit VR button)
- Wire up click handler to toggle panel visibility
- Add B-button positioning logic to reposition panel with other UI elements
- Pass DiagramMenuManager reference to Toolbox.setXR() for panel access

The panel now has complete skeleton structure and can be tested in VR:
- Click "Config" button on toolbox to show/hide panel
- Grab handle to reposition and test ergonomics
- Press B-button to auto-lower panel if too high
- 2m x 1.5m panel size optimized for VR viewing distance

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 12:03:24 -06:00
aa41895675 Add VR configuration panel implementation plan and Phase 1 foundation
Create VRCONFIGPLAN.md with comprehensive 10-phase implementation guide for building an immersive WebXR configuration panel using AdvancedDynamicTexture.

Implement Phase 1: Core panel setup
- Create VRConfigPanel class following Handle pattern for grabbability
- Set up 2m x 1.5m plane mesh with high-resolution ADT (2048x2048)
- Initialize main StackPanel container with title
- Add show/hide/dispose methods for panel lifecycle
- Integrate with appConfigInstance observable for config changes
- Auto-parent to platform for world movement tracking

The panel starts hidden and provides foundation for adding configuration controls in subsequent phases (location snap, rotation snap, fly mode, snap turn, label rendering mode).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 11:53:47 -06:00
970f6fc78a Fix label positioning, add billboard mode, fix XR entry shift, and fix config system
Label Positioning Fixes:
- Fix labels accounting for mesh scaling using maximumWorld coordinates
- Labels now properly positioned on scaled objects (spheres, boxes, etc.)
- Restore world→local coordinate transformation in updateLabelPosition

Billboard Mode Implementation:
- Add configurable label rendering modes: fixed, billboard, dynamic, distance-based
- Implement billboard mode (labels always face camera using BILLBOARDMODE_Y)
- Add label rendering mode to AppConfig with default 'billboard'
- Add UI selector in ConfigModal for label rendering mode
- Observable pattern updates all existing labels when mode changes

XR Entry Positioning Fix:
- Synchronize desktop camera position to platform before entering XR
- Transfer camera world position and rotation to prevent scene shift
- Reset physics velocity on XR entry to prevent drift
- Add debug logging for position synchronization

Config System Architecture Fix:
- Create singleton appConfigInstance to ensure single source of truth
- Update DiagramObject to use singleton instead of creating instances
- Update DiagramManager to use singleton
- Fix ConfigModal to update AppConfig directly (was only updating legacy config)
- ConfigModal now triggers Observable notifications via appConfigInstance setters
- Maintain legacy config for backward compatibility
- Fixes issue where label rendering mode changes didn't take effect

Files Modified:
- src/diagram/diagramObject.ts - Label positioning, billboard mode, singleton config
- src/diagram/diagramManager.ts - Use singleton config
- src/util/appConfig.ts - Add labelRenderingMode, export singleton
- src/util/appConfigType.ts - Add LabelRenderingMode type
- src/react/pages/configModal.tsx - Update AppConfig directly, add label mode UI
- src/util/functions/groundMeshObserver.ts - Add camera position sync on XR entry
- public/api/user/features - Update test config
- package.json - Version bump

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 08:52:04 -06:00
c1503d959e Add configurable feature management system with JSON-based feature flags
Implement comprehensive feature toggle system allowing menu options and features
to be controlled via JSON configuration fetched from API endpoint or static files.

Core System:
- Create FeatureConfig type system with page, feature, and limit-based flags
- Add React Context (FeatureProvider) that fetches from /api/user/features
- Implement custom hooks (useFeatures, useIsFeatureEnabled, useFeatureLimit, etc.)
- Default config disables everything except home page

Integration:
- Update PageHeader to filter menu items based on page flags
- Add ProtectedRoute component to guard routes
- Update VR menu to conditionally render items based on feature flags
- Update CreateDiagramModal to enable/disable options (private, encrypted, invite)
- Update ManageDiagramsModal to use configurable maxDiagrams limit

Configuration Files:
- Add static JSON files for local testing (none, free, basic, pro tiers)
- Add dev proxy for /api/user/features endpoint
- Include README with testing instructions

Updates:
- Complete CLAUDE.md naming conventions section
- Version bump to 0.0.8-27

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 06:52:39 -06:00
6ea6eaaac7 Implement SVG-based dynamic connection arrows with toolbox color matching
Replace static arrow.png with dynamically generated SVG arrows that match
the source object's color from the toolbox palette.

Changes:
- Replace arrow.png loading with inline SVG generation (32x32 right-pointing triangle)
- Add CreateColoredTexture() method to generate arrows in any hex color
- Extract color from source mesh using three-priority fallback system:
  1. mesh.metadata.color (most reliable)
  2. sourceMesh.id parsing (e.g., "tool-#box-template-#FF0000")
  3. material color extraction (backwards compatibility)
- Match extracted color to closest of 16 toolbox colors using Euclidean distance
- Track all textures in Set for synchronized animation
- Add proper texture disposal to prevent memory leaks

Benefits:
- No external arrow.png dependency
- Connections visually match their source object's toolbox color
- Consistent 16-color palette across all connections
- Efficient texture sharing for matching colors
- SVG scales perfectly at any resolution

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 06:24:42 -06:00
2915717a3a Adjust ResizeGizmo handle appearance and sizing
Updated handle visual properties:
- Changed handle color from yellow to blue for better visibility
- Increased base handle size from 20% to 50% of corner distance
- Modified distance-based scaling multiplier from 0.2 to 1.0 for improved depth perception
- Removed unused HANDLE_SIZE constant

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 04:57:22 -06:00
6643379133 Add distance-based scaling to ResizeGizmo handles
- Implement camera distance-based scaling so handles appear consistent visual size
- Add updateHandleScaling() method called every frame
- Use 0.2 multiplier for scale factor (adjustable)
- Handles maintain mesh rotation alignment (not billboarded)
- Keeps existing utility layer configuration for compatibility

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 20:10:14 -06:00
1ab3deae92 Add face handles and transform tracking to ResizeGizmo
- Add 6 face handles for single-axis scaling (in addition to 8 corner handles for uniform scaling)
- Implement single-axis scaling for face handles vs uniform scaling for corners
- Add automatic handle position updates when target mesh moves or rotates
- Track mesh transform changes using quaternions for accurate rotation detection
- Update handles in real-time during scaling to match new bounding box
- Add FACE_POSITIONS constant array to enums.ts
- Fix handle sizing to use consistent size calculation for all handles

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 17:06:32 -06:00
016b1fe6e2 Rebuild ResizeGizmo from scratch with simplified corner-only approach
Completely rewrote ResizeGizmo to be methodical and debuggable:

- Created CORNER_POSITIONS static array with normalized coordinates
- 8 corner handles only (removed face handles for simplicity)
- Handle sizing based on bbox distance (20% of corner-to-center)
- Handle positioning uses vectorsWorld directly
- XR controller ray picking in utility layer
- Edge rendering for hover (white) and grab (blue) states
- Virtual stick scaling: fixed-length ray from controller
- Uniform scaling based on distance ratio
- Snap to 0.1 increments on release only
- Proper XR input setup for existing and new controllers

Key improvements:
- Uses BabylonJS vectorsWorld instead of manual calculations
- Cleaner separation of concerns (picking, input, scaling)
- All private fields use underscore prefix convention
- Better haptic feedback (hover pulse, grab pulse, release pulse)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 16:40:56 -06:00
ebad30ce4d Implement Virtual Stick scaling with modular ResizeGizmo architecture
Refactored ResizeGizmo into modular structure:
- ResizeGizmo.ts: Main implementation with Virtual Stick scaling
- enums.ts: HandleType and HandleState enums
- types.ts: TypeScript interfaces
- index.ts: Barrel exports

Implemented Virtual Stick scaling approach:
- Fixed-length virtual stick extends from controller forward
- Scaling based on distance ratio in mesh local space
- World-to-local coordinate transforms for proper rotation handling
- Smooth continuous scaling during drag (no rounding)
- Snap to 0.1 increments on grip release
- Face handles: round only scaled axis
- Corner handles: round uniformly on all axes

Fixed scaling oscillation issues:
- Freeze handle position updates during active scaling
- Prevents feedback loop between scaling and handle positioning
- Use absoluteRotationQuaternion for proper handle rotation

Added WebXRDefaultExperience parameter to constructor for proper controller integration with manual ray casting in world space.

Added test shortcuts:
- Ctrl+Shift+T: Create test entities (sphere and box)
- Ctrl+Shift+X: Clear all entities

Wired Close button to dispose active ResizeGizmo.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-16 11:43:17 -06:00
0712abe729 Remove old ResizeGizmo integration code from AbstractController
Clean up AbstractController by removing references to old ResizeGizmo implementation:
- Remove utility layer mesh filtering logic
- Remove auto-show gizmo on hover
- Remove gizmo handle click filtering
- Remove unused Ray import
- Bump version to 0.0.8-26

These changes complete the migration to the new simplified ResizeGizmo architecture.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-16 05:55:17 -06:00
2c3fba31d3 Reimplement ResizeGizmo as simplified single-file XR gizmo
Complete rewrite of ResizeGizmo with a much simpler architecture:
- Single file implementation (index.ts) replacing multi-file system
- 14 handles: 6 face handles for single-axis scaling, 8 corner handles for uniform scaling
- XR-only interaction using UtilityLayerRenderer
- Billboard scaling for constant screen-size handles
- Grip-based interaction with hover/active visual states (gray/white/blue)
- Single-axis scaling from opposite face (fixed pivot)
- Uniform scaling from center
- Integrated with ClickMenu Size button
- Observable events (onScaleEnd, onScaleDrag) for future integration

Removed old complex implementation files and simplified documentation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-16 05:53:26 -06:00
c815db4594 Improve ResizeGizmo hover state and handle interaction
Phase 1 & 2: Handle positioning and wireframe improvements
- Move handles 5% outward from bounding box (was inward)
- Rename boundingBoxPadding → handleOffset for clarity
- Add wireframePadding (3% breathing room around mesh)

Hover boundary detection (prevent loss in whitespace):
- Add isPointerInsideHandleBoundary() with ray-AABB intersection
- Use local space transformation for accurate OBB handling
- Keep HOVER_MESH state when pointer in handle boundary
- Fix: Trust ResizeGizmo state instead of recreating with fake rays

Prevent main scene mesh grab during handle interaction:
- Add ResizeGizmo state check in pointer observable
- Add defense-in-depth guard in grab() method
- Prevents controller from grabbing diagram mesh when hovering handle
- Two-level protection against race conditions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-15 13:55:18 -06:00
43100ad650 Fix color persistence using metadata and source mesh ID fallback
Colors were being lost during resize operations because toDiagramEntity
extracted color from material.diffuseColor, which is no longer used after
the emissiveColor rendering optimization (commit c7887d7).

Root Causes:
1. Rendering system changed from diffuseColor to emissiveColor
2. Material properties unreliable when materials are shared
3. Material-based extraction broke when properties changed

Solution - Three-Tier Fallback Chain:

Priority 1: mesh.metadata.color
- Most reliable, explicitly set during mesh creation
- Already populated by buildMeshFromDiagramEntity (line 163)

Priority 2: Extract from mesh.sourceMesh.id (InstancedMesh)
- Tool mesh IDs encode color: "tool-BOX-#FF0000"
- Preserves original tool color regardless of material state
- Works for all instanced diagram meshes

Priority 3: Material properties (backwards compatibility)
- Checks emissiveColor first (current system)
- Falls back to diffuseColor (old system)
- Handles both StandardMaterial and PBRMaterial
- Maintains compatibility with non-instanced meshes

Changes:
- Import InstancedMesh from @babylonjs/core
- Replace direct material extraction with fallback chain
- Parse tool mesh ID to extract hex color code
- Normalize colors to lowercase
- Add null checks for safe color extraction

Benefits:
 Independent of material system changes
 Works with shared materials
 Preserves original tool colors
 Backwards compatible
 More reliable than material-only extraction

Files modified:
- toDiagramEntity.ts: Implement fallback chain for color extraction

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-15 08:05:49 -06:00
af52d5992c Fix handle rotation to properly match mesh orientation
Handle meshes now correctly rotate to match the target mesh's world-space
orientation instead of appearing axis-aligned.

Root Cause:
- Handle positions from HandleGeometry are calculated in world space
- Setting mesh.position treats values as local space
- This created coordinate system mismatch when rotation was also set
- Result: rotation appeared to have no effect

Solution:
- Extract rotation from mesh world matrix using quaternion decomposition
- Set rotation FIRST (before position)
- Use setAbsolutePosition() for world-space positioning
- This ensures rotation and position work correctly together

Changes:
- Import Quaternion from @babylonjs/core
- Update createHandleMeshes(): decompose world matrix, set rotation,
  then use setAbsolutePosition()
- Rename updateHandlePositions() to updateHandleTransforms()
- Update updateHandleTransforms(): same rotation-then-position approach
- Add null check for _targetMesh in updateHandleTransforms()

Technical Details:
- computeWorldMatrix(true) gets complete transform including parent
- decompose() extracts pure rotation as quaternion (avoids gimbal lock)
- setAbsolutePosition() correctly handles world-space coords with rotation
- Order matters: rotation before position for correct transformation

Result:
 Handle box shapes visually tilt/rotate with mesh
 Handles remain correctly positioned on OBB
 Both wireframe and individual handles rotate together

Files modified:
- ResizeGizmoVisuals.ts: Handle rotation implementation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-15 07:36:57 -06:00
5fbf2b87c1 Implement OBB-based scaling for rotated meshes and simplify gizmo UX
Major improvements to ResizeGizmo rotation handling and interface:

1. **OBB (Oriented Bounding Box) Implementation**
   - Replace AABB with true OBB that rotates with mesh
   - Calculate 8 OBB corners in world space using mesh world matrix
   - Update bounding box wireframe to use OBB corners
   - Rewrite all handle generation (corner, edge, face) for OBB positioning
   - Handle normals now calculated from mesh center to handle position
   - Result: Bounding box and handles rotate with mesh, scaling follows local axes

2. **Simplify UX - Remove Edge Handles**
   - Remove TWO_AXIS mode from ResizeGizmoMode enum
   - Disable edge handles (green, two-axis) to reduce cognitive complexity
   - Keep only corner handles (blue, uniform) and face handles (red, single-axis)
   - Updated from 26 total handles to 14 handles (6 face + 8 corner)
   - All scaling capabilities still available through remaining handle types

3. **Fix Event Leak-Through (Hit Testing)**
   - Add getUtilityScene() method to ResizeGizmoManager
   - Configure XR pick predicate to exclude utility layer meshes (primary defense)
   - Filter utility layer in pointer observable (secondary defense)
   - Filter utility layer in click handler (tertiary defense)
   - Prevents gizmo handle events from leaking to main scene

4. **Documentation**
   - Add TODO.md documenting implementation and decisions
   - Document OBB implementation and edge handle removal
   - Track completed features and rationale

Files modified:
- ResizeGizmoVisuals.ts: OBB wireframe and corner calculation
- HandleGeometry.ts: OBB-based handle positioning for all types
- ResizeGizmoConfig.ts: Disable edge handles
- ResizeGizmoManager.ts: Add utility scene access
- ScalingCalculator.ts: Uniform two-axis scaling (distance-ratio)
- types.ts: Remove TWO_AXIS mode
- diagramMenuManager.ts: XR pick predicate filtering
- abstractController.ts: Pointer and click filtering
- TODO.md: Documentation of changes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-14 07:06:06 -06:00
204ef670f9 Move ResizeGizmo handles inside bounding box for better selection
- Reverse padding direction in HandleGeometry:
  - Corner handles now positioned inward (add padding to min, subtract from max)
  - Edge handles now positioned inward (same reversal)
  - Face handles now positioned inward (same reversal)
- Remove padding from bounding box wireframe to match mesh bounds
- Handles are now 5% inside edges instead of 5% outside
- Improves handle selection reliability
- Prevents unwanted gizmo auto-hide when pointer moves to handles

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-13 18:00:05 -06:00
26b48b26c8 Implement WebXR resize gizmo with virtual stick scaling and extract adapter to integration layer
- Implement comprehensive WebXR resize gizmo system with three handle types:
  - Corner handles: uniform scaling (all axes)
  - Edge handles: two-axis planar scaling
  - Face handles: single-axis scaling
- Use "virtual stick" metaphor for intuitive scaling:
  - Fixed-length projection from controller to handle intersection
  - Distance-ratio based scaling from mesh pivot point
  - Works naturally with controller rotation and movement
- Add world-space coordinate transformations for VR rig parenting
- Implement manual ray picking for utility layer handle detection
- Add motion controller initialization handling for grip button
- Fix color persistence bug in diagram entities:
  - DiagramEntityAdapter now uses toDiagramEntity() converter
  - Store color in mesh metadata for persistence
  - Add dependency injection for loose coupling
- Extract DiagramEntityAdapter to integration layer:
  - Move from src/gizmos/ResizeGizmo/ to src/integration/gizmo/
  - Add dependency injection for mesh-to-entity converter
  - Keep ResizeGizmo pure and reusable without diagram dependencies
- Add closest color matching for missing toolbox colors
- Handle size now relative to bounding box (20% of avg dimension)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-13 17:52:23 -06:00
02c08b35f2 Add comprehensive material sharing validation and diagnostics
Implemented extensive logging and validation to diagnose material sharing issues and prevent unnecessary material creation:

**Validation Added:**
- Pre-creation check: Verify tool meshes have materials before creating instances
- Early exit if tool mesh lacks material to prevent bad instances
- Post-creation validation in buildColor.ts to catch tool creation issues

**Enhanced Diagnostics:**
- Detailed debug logging for tool mesh lookup and instance creation
- Error logging with full context when material sharing fails
- Source mesh material validation for InstancedMesh
- Lists available tool meshes when lookup fails

**Statistics Tracking:**
- Tracks instances created vs materials shared
- Counts fallback material creations
- Logs sharing rate every 10 instances (target: 100%)
- Helps identify material sharing failures in production

**Expected Outcome:**
- 100% material sharing rate for tool-based entities
- Zero fallback material creations
- All instances inherit materials from tool templates
- Better draw call batching (same material = batched rendering)

This diagnostic infrastructure will identify:
1. Timing issues (tools not ready when entities created)
2. Tool mesh creation failures
3. BabylonJS InstancedMesh material inheritance issues

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-13 11:18:07 -06:00
bda0735c7f Add WebXR rendering mode toggle with 4 modes
Implemented a single button in the toolbox that cycles through four rendering modes:
1. Lightmap + Lighting - diffuseColor + lightmapTexture with lighting enabled
2. Emissive Texture - emissiveColor + emissiveTexture with lighting disabled (default)
3. Flat Color - emissiveColor only with lighting disabled
4. Diffuse + Lights - diffuseColor with two dynamic scene lights enabled

Features:
- Single clickable button displays current mode and cycles to next on click
- Automatically manages two scene lights (HemisphericLight + PointLight) for Diffuse + Lights mode
- UI materials (buttons, handles, labels) are excluded from mode changes to remain readable
- Button positioned below color grid with user-adjusted scaling
- Added comprehensive naming conventions documentation
- Updated inspector hotkey to Ctrl+Shift+I

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-13 10:36:03 -06:00
c7887d7d8f Optimize lightmap rendering using emissive texture approach
Changed from lightmapTexture with lighting enabled to emissiveTexture with lighting disabled for better performance. The new approach provides the same lighting illusion without expensive per-pixel lighting calculations.

- Added LightmapGenerator.ENABLED toggle for performance testing
- Updated buildColor.ts to use emissiveColor + emissiveTexture with disableLighting = true
- Updated buildMissingMaterial() to match new rendering approach
- Fixed buildTool.ts to access emissiveColor instead of diffuseColor for material color detection

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-13 09:44:56 -06:00
3f02fc7ea5 Implement lightmap-based rendering for performant lighting illusion
Replace emissive-only rendering with diffuse + lightmap system to achieve realistic lighting appearance without dynamic light overhead.

- Create LightmapGenerator class with canvas-based radial gradient generation
- Generate one lightmap per color (16 total) using top-left directional light simulation
- Cache lightmaps in static Map for reuse across all instances
- Preload all lightmaps at toolbox initialization for instant availability
- Update buildColor() to use diffuseColor + lightmapTexture instead of emissiveColor
- Update buildMissingMaterial() to use lightmap-based rendering
- Enable lighting calculations (disableLighting = false) to apply lightmaps

Lightmap details:
- 512x512 resolution RGBA textures
- Radial gradient: center (color × 1.5), mid (base color), edge (color × 0.3)
- Simulates top-left key light with smooth falloff
- Total memory: ~16 MB for all lightmaps
- Zero per-frame performance cost

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-13 09:20:40 -06:00
100c5e612c Move exit XR button to toolbox class
Refactored exit XR button creation from rigplatform to toolbox for better organization and UI cohesion.

- Add setXR() methods to DiagramManager, DiagramMenuManager, and Toolbox to pass WebXRDefaultExperience after initialization
- Create setupXRButton() in Toolbox class that creates button when entering XR
- Position button at bottom-right of toolbox (x: 0.5, y: -0.35, z: 0)
- Use Y-axis rotation (Math.PI) for correct orientation within toolbox coordinate system
- Scale button to 0.2 for appropriate size
- Remove button creation code from rigplatform

Exit button now moves with toolbox and is logically grouped with other UI elements.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-13 06:57:35 -06:00
d59c7b6e6e Enable per-instance edge rendering for hover effects
Changed EdgesRenderer to work on individual instances instead of source mesh to prevent all instances from highlighting when one is hovered.

- Remove edgesShareWithInstances flag (was causing all instances to highlight)
- Enable/disable edges directly on hovered instance
- Adjust edge width to 0.2 and color to pure white for cleaner appearance
- Remove metadata tracking in favor of checking edgesRenderer directly

This ensures only the specific hovered entity shows visual feedback while maintaining haptic feedback for all interactions.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-13 05:49:54 -06:00
0ad61bdde9 Fix XR component positioning to appear in front of user
- Use camera.getDirection() instead of manual Euler angle calculation to properly account for camera transform hierarchy
- Negate forward offsets to position objects in -Z direction (user faces -Z by design)
- Replace expensive HighlightLayer hover effect with lightweight EdgesRenderer (20-50x faster)
- Add comprehensive debug logging for position calculations

The camera has a parent transform with 180° Y rotation, causing the user to face -Z in world space. Components now correctly position in front of the user when entering XR.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-12 22:41:51 -06:00
4a9d7acc41 Optimize connection raycasting with position caching
Performance improvements:
- Added Vector3 position caching for connection endpoints
- Only update connections when meshes actually move (>0.001 units)
- Use DistanceSquared for efficient movement detection
- Replace inefficient vector length comparison

Impact:
- Static connections: 0 raycasts/second (was ~20/sec per connection)
- With 10 connections: 90-99% reduction in raycast operations
- Eliminates unnecessary curve geometry recreation

Implementation:
- Added _lastFromPosition and _lastToPosition caching
- Created hasConnectionMoved() method with tolerance threshold
- Reset cache on mesh removal and initial setup
- Clean up cache in disposal method

This dramatically reduces CPU usage in VR with multiple connections.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-12 21:46:57 -06:00
6ad04bb21a Refactor config naming and upgrade dependencies
Config changes:
- Renamed gridSnap to locationSnap for clarity
- Fixed configMenu to reference correct property
- Added debug logging to setAppConfig

Code cleanup:
- Removed commented duplicate exitXR call

Dependencies:
- Upgraded @babylonjs packages from 7.21.5 to 8.16.2
- Upgraded @mantine packages from 7.12.0 to 7.17.8

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-12 21:36:56 -06:00
293c74d7c1 Remove debug console.log from render loop
Removed console.log() from connectionPreview render observer that was
executing every frame during connection dragging. This eliminates I/O
blocking and stringification overhead in the critical VR render path.

Performance: Quick win for VR framerate improvement.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-12 21:29:24 -06:00
6d2049e1f6 Convert to unlit rendering and fix connection update error
Lighting changes:
- Disabled HemisphericLight in customEnvironment
- Changed all materials from diffuse to emissive colors
- Added disableLighting=true to all StandardMaterials
- Updated toolbox colors, diagram entities, and spinner

Bug fix:
- Fixed "Cannot read properties of undefined (reading 'pickedMesh')" error
- Added defensive check in DiagramObject.updateConnection()
- Now validates hit array has at least 2 elements before accessing

Materials now render at full brightness with unlit/flat shading.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-12 21:16:29 -06:00
cf0f359921 Position UI components relative to camera on XR entry
When entering immersive mode, toolbox and input text view now position
themselves relative to the user's initial camera position:
- Toolbox: 0.5m ahead, 0.5m below, 0.2m to the left
- Input text view: 0.5m ahead, 0.5m below (centered)

Uses camera world Y position to ensure vertical offset is consistent
regardless of head pitch/tilt when entering XR.

Also added CLAUDE.md documentation for the codebase.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-12 20:22:29 -06:00
58668443c4 Fix initialization errors when navigating to db/public/local
- Fix null reference error in buildColor.ts by initializing metadata.tools array
- Add physics engine availability check in buildRig to prevent PhysicsAggregate creation before engine is ready
- Remove duplicate scene initialization by eliminating redundant initializeEngine() call
- These fixes resolve WebGL shader compilation errors and prevent app crashes

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-15 16:25:15 -05:00
9d5234b629 Added webxr exit button 2025-02-14 11:01:27 -06:00
5ce0c9ce4f Changed menu to be consistent between mini and main size. 2024-11-22 09:25:22 -05:00
8c04b40d03 Added Branding + Auth. 2024-08-30 14:57:29 -05:00
cdf59db5b6 Updated config page. 2024-08-30 14:56:13 -05:00
f2b9e78e45 Updated config page. 2024-08-30 12:43:19 -05:00
4e6c3a63d0 Updated config page. 2024-08-30 12:43:19 -05:00
e69d008bfa Added 404 handler, changed page db update. 2024-08-30 12:43:19 -05:00
5d3cad0def Reintegrated VR compnent. 2024-08-30 12:43:19 -05:00
4f39030ed4 Disabled service worker, enhanced management console. 2024-08-30 12:43:19 -05:00
2397ddcd4c Updated UI to use Mantine. 2024-08-30 12:43:19 -05:00
b9152678b8 Removed dead code. 2024-08-30 12:43:19 -05:00
a9c8d3dbad Removed dead code. 2024-08-30 12:43:19 -05:00
60758ed84d Removed dead code. 2024-08-30 12:43:19 -05:00
53ca47d63e
Update node.js.yml 2024-08-30 12:18:10 -05:00
afdf765a8f Forgot arrow image. 2024-08-23 10:27:41 -05:00
71da2dd6a2 chnaged preview to match current connection style. 2024-08-23 10:25:26 -05:00
17206abca7 Added target sphere to pick preview 2024-08-23 10:12:07 -05:00
263879d215 Refactored private variables rig platform. 2024-08-23 10:11:03 -05:00
4cb50e5c6a Fixed initial camera rotation when entering XR. 2024-08-23 09:35:28 -05:00
ba2d9a7886 Added Directional arrows to connectors. 2024-08-23 09:26:44 -05:00
83279fa5b0 Added highlighting. 2024-08-23 08:50:00 -05:00
b443e1854b Fixed animation direction for connections. 2024-08-22 19:14:44 -05:00
c00fc55462 Moved label for connections. 2024-08-22 18:27:46 -05:00
b198605643 CHanged connector style 2024-08-22 18:21:08 -05:00
2486107041 updated linting warnings, removed unused variables. 2024-08-03 19:16:32 -05:00
a07b53f2a7 refactored web interface, updated image update code. 2024-08-03 19:12:32 -05:00
1d6c82a16a Updated db create event detail 2024-07-17 15:57:03 -05:00
1de6270f79 updated encryption to only encrypt when password is set. 2024-07-17 15:29:57 -05:00
4fdcc9694d Changed labels to help with export to glb. 2024-06-18 09:12:10 -05:00
4c300dc73b reformatted tool arrangement. Added diffusetexture to label. Added exportable to object parents. 2024-06-17 14:17:45 -05:00
e0d85a6a3d Added stl person to toolbox. 2024-06-17 11:33:24 -05:00
d08e86e92f Added websocket presence. 2024-06-14 09:38:16 -05:00
4e1436b0cc Added smart versioning 2024-06-11 17:00:16 -05:00
cf278fed3a Added smart versioning 2024-06-11 16:58:35 -05:00
ae73f3e74b Added smart versioning 2024-06-11 16:51:27 -05:00
540658e3d0 Added smart versioning 2024-06-11 16:49:20 -05:00
1d94143b21 Added smart versioning 2024-06-11 16:46:42 -05:00
648876c06b fixed some race conditions. 2024-06-11 16:16:57 -05:00
bb9c3ec396 updated security 2024-06-11 12:19:38 -05:00
dec0041c21 updated security 2024-06-11 12:17:28 -05:00
a334f13e6f added events 2024-06-11 07:20:25 -05:00
da38df7df4 readded pouch-find 2024-06-11 07:07:48 -05:00
2d1a3ba5d6 added row level AES encryption. 2024-06-10 18:22:17 -05:00
3d3f73c259 added row level AES encryption. 2024-06-10 15:52:45 -05:00
d6941fd1bf added view only flag. 2024-06-07 09:21:18 -05:00
4a95028fe8 performance optimization, reduced face counts. 2024-06-07 08:24:31 -05:00
ffe8f60f38 performance optimization, user manager + afterRender were slowing things down. 2024-06-06 19:44:16 -05:00
9e7833b149 added ground mesh try catch. 2024-06-06 10:33:59 -05:00
e405dc1598 added prod sourcemaps. 2024-06-06 10:16:49 -05:00
cb2675bf27 added prod sourcemaps. 2024-06-06 10:06:06 -05:00
724cd79ab3 Removed html renderer, too may problems. 2024-06-06 09:22:39 -05:00
f07ea11817 Stupid hack due to race condition with click menu. 2024-06-05 16:01:49 -05:00
7315e3397a Refactored some larger classses...added enhanced map capability. 2024-06-03 08:34:48 -05:00
7806760153 removed unnecessary parameter. 2024-06-01 09:35:55 -05:00
7561a06b69 Created new interactive scaling menu. 2024-06-01 09:28:21 -05:00
06333e9123 Refactored things to be closer together in domain. 2024-05-31 19:38:30 -05:00
d8d91dd688 Fix connecter and controller bug. 2024-05-31 14:14:28 -05:00
fa8865d013 Fix duplicator functionality. 2024-05-30 11:34:22 -05:00
fbafd747d3 Fix duplicator functionality. 2024-05-30 11:29:07 -05:00
63d1e627ad Added more logging. 2024-05-30 10:23:51 -05:00
1a3e9b879e Added clickMenu cleanup when multiple clicks. 2024-05-30 10:14:42 -05:00
d0b08b72e2 made connections not grabbable. 2024-05-30 10:11:07 -05:00
f26aa01211 Added Orphan connection cleanup and logging. 2024-05-30 10:08:04 -05:00
6ec28efe78 Updated way that connections are previewed. 2024-05-30 09:22:32 -05:00
cdaff97614 small change to formatting/fixed problem with mesh removal removing shared materials. 2024-05-29 09:13:06 -05:00
e85adc1386 Updated basic grab/drop. 2024-05-28 15:33:44 -05:00
2872026ac9 Upated babylonjs version, added map, camera, camerawindow. 2024-05-21 06:34:48 -05:00
4c26dca6c5 updated web styles. 2024-05-10 13:33:30 -05:00
15fdb938ee Added SW cache clear mechanism. 2024-05-08 09:17:06 -05:00
bbe54dc3e3 Added background noise. Refactor customEnvironment.ts 2024-05-08 07:55:42 -05:00
01874b9e9e Slight simplification refactor (no functional change) 2024-05-07 19:40:16 -05:00
3ade3d4d6a Slight simplification refactor (no functional change) 2024-05-07 19:33:06 -05:00
24ae4aad41 Updated icon 2024-05-07 10:29:47 -05:00
ae3a94b8d4 Updated icon 2024-05-07 10:01:10 -05:00
735bd4bb2f updated service worker. 2024-05-07 09:49:58 -05:00
41aeceed69 updated service worker. 2024-05-07 09:40:08 -05:00
5a1c86a0dd updated service worker. 2024-05-07 09:33:04 -05:00
7ccca76119 updated service worker. 2024-05-07 09:20:38 -05:00
2debefd556 updated manifest.webmanifest and images. 2024-05-07 08:06:24 -05:00
c86ed5e9a0 resized images 2024-05-07 07:54:59 -05:00
ce9316d20a resized images 2024-05-07 07:46:59 -05:00
1e36ca20d9 moved scene inspector. 2024-05-04 17:12:35 -05:00
0acde00ecc moved scene inspector. 2024-05-04 17:12:15 -05:00
d8cdb019fb moved scene inspector. 2024-05-04 17:06:29 -05:00
bf7d419df2 moved script loading. 2024-05-03 15:37:01 -05:00
fd774c0be2 Fixed clickmenu positioning. 2024-05-03 15:10:16 -05:00
08569de94d Fixed left controller typo. 2024-05-02 05:49:14 -05:00
d864c2e562 Fixed left controller typo. 2024-05-01 13:38:04 -05:00
a016aa749b Fixed Circular Dependency. 2024-05-01 09:11:10 -05:00
b788b64df5 Fixed Circular Dependency. 2024-05-01 08:34:25 -05:00
dc3c3c56a1 Version Bump. 2024-04-30 09:31:44 -05:00
1dd192cd4d added initial gltf test. 2024-04-30 06:48:37 -05:00
d82df88296 Added Image upload component. 2024-04-29 13:39:05 -05:00
eb4281ac30 Moved loggers out of global module scope. 2024-04-28 11:07:03 -05:00
e27a77d674 Moved loggers out of global module scope. 2024-04-28 09:11:19 -05:00
2f29b0a2de Moved loggers out of global module scope. 2024-04-28 09:03:20 -05:00
791481e564 Added Metadata sync for friendly name 2024-04-27 06:51:24 -05:00
0e4d815225 Added Metadata sync for friendly name 2024-04-27 06:50:04 -05:00
f479f6043f Added Metadata sync for friendly name 2024-04-26 07:05:46 -05:00
4db349581b Updated Tutorial. 2024-04-25 12:38:29 -05:00
d761a59d6d removed button help from controllers. 2024-04-24 14:27:05 -05:00
36e4b04957 updated menu positioning, added b-button to enable resetting positions. 2024-04-24 14:24:22 -05:00
5c22c15076 Bumped Version to .3 2024-04-24 12:16:03 -05:00
48c0535c8f Tweak to allow localdb not to sync to cloud and generate unique IDs for databases. 2024-04-24 11:50:23 -05:00
f87190af86 Fixed platform specific dependencies. 2024-04-24 08:02:15 -05:00
d17fc0897d Introduction revamp. 2024-04-24 07:52:28 -05:00
cfe174d564 Introduction revamp. 2024-04-24 07:50:26 -05:00
fbc39f2103 Introduction revamp. 2024-04-24 07:19:15 -05:00
c81dd8c24a Added Diagram Menu Manager. 2024-04-23 10:38:29 -05:00
e30bca5090 Refactor digramManager observer mask, updated logger in controllerbase. 2024-04-23 09:10:26 -05:00
f9127df48a Fixed grid and rotation snapping to be more sensible. 2024-04-21 07:51:15 -05:00
81c61fc6f8 Fixed grid and rotation snapping to be more sensible. 2024-04-20 17:22:14 -05:00
4b06cb2679 Fixed grab and clone to replicate properly. Changed connection size to match size menu control. 2024-04-20 08:05:09 -05:00
b2c5c85d7e Updated click menu to use Html Button. Added Scale feature. 2024-04-20 06:56:42 -05:00
ea5b8789c0 Added babylon-html, updated db to save values correctly in local storage. 2024-04-19 10:36:35 -05:00
2387fe53ef Updated how labels and text meshes are created/updated. Removed duplicates. 2024-04-17 15:00:52 -05:00
469d4a5116 DefaultScene static method to be upper case...added webGPU support (disabled as there appears to be a bug). 2024-04-17 08:18:35 -05:00
8bb77873cc Refactored scene to use DefaultScene. 2024-04-16 13:29:42 -05:00
b8521be13e Refactored scene to use DefaultScene. 2024-04-16 13:23:58 -05:00
f7dd6840e7 Refactored scene to use DefaultScene. 2024-04-16 13:21:00 -05:00
a1248a2e34 Refactored scene to use DefaultScene. 2024-04-16 11:14:32 -05:00
d7e812d253 Removed sounds, beginning to refactor how sound interactions are handled. 2024-04-16 10:03:57 -05:00
8cf12bc91c updated menu placement and handle class. 2024-04-15 16:14:19 -05:00
888e858578 Simplified arguments passed. 2024-04-15 11:45:53 -05:00
b59ad398db Simplified arguments passed. 2024-04-15 11:40:31 -05:00
9708cedf30 updated text label position on connections. added nullcheck for modify event. 2024-04-15 11:31:45 -05:00
6e5afa2dac updated text label position on connections. added nullcheck for modify event. 2024-04-15 11:23:10 -05:00
b459f75d24 updated text label position on connections. added nullcheck for modify event. 2024-04-15 09:22:50 -05:00
e2d251e8b7 Updated manifest.webmanifest 2024-04-15 08:48:03 -05:00
9c76c7537d Fixed up logging, changed connector to use material from "from" mesh. 2024-04-15 08:42:36 -05:00
f148805e80 updated connection text node positioning. 2024-04-14 18:42:33 -05:00
4f820a4f09 Updated click menu rotation and connection endpoint positioning. 2024-04-14 13:43:01 -05:00
12bc90d44f
Update node.js.yml 2024-04-13 16:54:31 -05:00
1116f1ae3f fixed label location for connections. 2024-04-13 13:01:35 -05:00
c21fd15815 Removed unused function 2024-04-13 11:40:07 -05:00
c6e52138b3 changed main menu order and behavior, updated text node handling, refactored logging from console.log. 2024-04-13 11:34:25 -05:00
159e687c19 Simplified interactions, changed menu interactions for changing entities. 2024-04-12 13:33:52 -05:00
cabc38ce09 Simplified interactions, changed menu interactions for changing entities. 2024-04-12 07:04:02 -05:00
727977d5c6 Simplified interactions, changed menu interactions for changing entities. 2024-04-10 16:36:36 -05:00
2d3855621e refactored controllers and diagram manager. 2024-04-09 15:01:35 -05:00
9a9b865843 refactored logging. 2024-04-03 10:10:01 -05:00
1d003c77e6 refactored logging. 2024-04-03 10:09:38 -05:00
c4d201c5ba refactored logging. 2024-04-03 10:08:17 -05:00
c1ba8f5e10 refactored logging. 2024-04-03 10:05:23 -05:00
5eaf5f37e3 refactored logging. 2024-04-03 10:02:10 -05:00
c87472b722 refactored logging. 2024-04-03 09:59:16 -05:00
2c03ad1123 changed web app to hide when inspector opens. 2024-04-02 14:42:25 -05:00
36fa46cccb Added description meta tag 2024-04-02 14:25:28 -05:00
73dae9c1cd Re added quest link. 2024-04-02 14:19:27 -05:00
d212281ee8
Update node.js.yml 2024-04-02 10:12:02 -05:00
a2cfbb17f5
Update node.js.yml 2024-04-02 10:05:55 -05:00
352491b743
Update node.js.yml 2024-04-02 09:54:16 -05:00
3439962d6a
Update node.js.yml 2024-04-02 09:51:13 -05:00
772b84250b
Update node.js.yml 2024-04-02 09:43:29 -05:00
9d91e7bdb8
Update node.js.yml 2024-04-02 09:35:04 -05:00
8dfdae0c51
Update node.js.yml 2024-04-02 09:32:44 -05:00
7933b6542c
Update node.js.yml 2024-04-02 09:30:24 -05:00
f69bdac7b9
Update node.js.yml 2024-04-02 09:23:40 -05:00
2d61cd1563
Update main.yml 2024-04-02 09:23:03 -05:00
85bfcdbf6a
Update node.js.yml 2024-04-02 09:20:57 -05:00
405d71ddbe
added upload 2024-04-01 08:07:12 -05:00
62012c7fe8
Added node dependency cache 2024-04-01 07:55:19 -05:00
f07ef4b62e
Update Heap to 4gb 2024-04-01 07:36:01 -05:00
16b690e400
Update node.js.yml 2024-04-01 07:34:12 -05:00
d9658c538d
Updated Config 2024-04-01 07:30:49 -05:00
659b71ec53
Create node.js.yml 2024-04-01 07:26:39 -05:00
7c6fa4eadc
Update main.yml 2024-04-01 07:18:45 -05:00
54ef9df770
Update main.yml 2024-03-26 08:28:54 -05:00
ebb5cede1b
Update main.yml 2024-03-26 08:26:40 -05:00
4df012d8ba
Update main.yml 2024-03-26 08:20:38 -05:00
4ce6f57998
Update main.yml 2024-03-26 08:05:30 -05:00
f9e6207f09
Update main.yml 2024-03-26 07:53:28 -05:00
85463eee5c
Update main.yml 2024-03-26 07:02:01 -05:00
c0fcd00536
Update main.yml 2024-03-26 06:57:52 -05:00
1450f7dc08
Update main.yml 2024-03-25 14:39:39 -05:00
f01bf50a96
Create main.yml 2024-03-25 14:21:49 -05:00
d053cb08ae Re added quest link. 2024-03-25 09:51:14 -05:00
d73b200d35 Updated metadata of connection parent to enable export. 2024-03-08 15:35:00 -06:00
73a850613d Updated menu system to be more consistent. Change toolbox to fixed colors; 2024-03-08 10:41:18 -06:00
1413a0bda9 Added scale menu button and screen shots. 2024-02-12 09:33:57 -06:00
618c0013ee split out react UI 2024-02-06 15:11:29 -06:00
74603cba65 split out react UI 2024-02-05 11:15:27 -06:00
e41f8194d5 Added 3d closet demo, cleaned up menus. 2024-02-05 10:46:23 -06:00
2ef5379a3b Added 3d closet demo, cleaned up menus. 2024-02-02 15:36:47 -06:00
27167f4e47 Reenabled gltf export 2024-01-23 07:11:59 -06:00
cdc47f8ac9 Refactor fly/walk mode. 2024-01-15 11:03:07 -06:00
a1adbf5bd9 experiments with glb objects and data based diagrams. 2023-12-23 06:21:58 -06:00
dcd5a8835c Added glb object type loader, cleaned up loggers and change web controller. 2023-12-12 14:00:39 -06:00
4c45ac5ef0 Added netlify _redirects 2023-12-12 07:11:07 -06:00
74b0ae464b added request headers to CORD response. 2023-12-12 06:53:14 -06:00
410a88099e Cleaned up menus and local environment. 2023-12-11 15:15:17 -06:00
7d5ef80b5b Updated initial camera position, fixed initial demo. 2023-12-11 10:14:46 -06:00
25a63ca182 Updated function, renamed main vr app. 2023-11-30 16:05:53 -06:00
72bdbf3ffa Removed some unused code. Optimized export bundle. Started building diagram selector menu. 2023-11-10 13:19:28 -06:00
237b127686 Removed some unused code. Optimized export bundle. 2023-11-09 08:04:13 -06:00
a2fc7ec460 Updated config and OPTIONS method for function. 2023-11-07 15:28:26 -06:00
57a5d03518 Updated config and OPTIONS method for function. 2023-11-07 15:14:28 -06:00
83a36d412e Updated config and OPTIONS method for function. 2023-11-07 14:58:30 -06:00
39485dc6e1 Updated config and OPTIONS method for function. 2023-11-07 14:44:54 -06:00
bd1d868749 Updated config and OPTIONS method for function. 2023-11-07 14:25:41 -06:00
3eab01027b Updated config and OPTIONS method for function. 2023-11-07 13:40:26 -06:00
8d5d20b703 Updated config and OPTIONS method for function. 2023-11-07 13:32:47 -06:00
606a51c767 Updated config and OPTIONS method for function. 2023-11-07 12:16:19 -06:00
fd93444ac4 Code Cleanup. 2023-11-07 10:27:21 -06:00
cee114f784 Code Cleanup. 2023-11-07 07:20:11 -06:00
c42772ce47 Changed endpoints to environment variables. 2023-11-07 06:55:57 -06:00
f51b5d9e7c Added upsert for new users. 2023-11-06 09:44:32 -06:00
8460470eb7 Refactored code. 2023-11-06 05:48:22 -06:00
8039962dbf Refactored code. 2023-11-06 05:39:30 -06:00
80ef1b595f Refactored code. 2023-11-06 05:32:01 -06:00
1ae2dd3609 Updated user function to new api. 2023-11-04 12:00:11 -05:00
d7d8b414e5 Updated OPTIONS method for user function 2023-10-31 12:49:36 -05:00
bb93e502c9 Updated OPTIONS method for user function 2023-10-17 12:25:58 -05:00
71128abb5f Updated OPTIONS method for user function 2023-10-17 12:19:56 -05:00
acafe25deb Updated OPTIONS method for user function 2023-10-17 12:11:37 -05:00
900ed963c0 Updated OPTIONS method for user function 2023-10-17 10:24:53 -05:00
bd63083ee3 Updated user function 2023-10-13 14:59:24 -05:00
af1fdad045 Updated user function 2023-10-13 13:34:40 -05:00
d46b32b0d7 Updated user function 2023-10-13 13:19:49 -05:00
eda8826704 Updated user function 2023-10-13 13:13:46 -05:00
ef394bab64 Updated user function 2023-10-13 13:04:24 -05:00
338df0f14d Updated user function 2023-10-13 13:00:09 -05:00
561603da1d Updated user function 2023-10-13 12:39:29 -05:00
700330767f Updated user function 2023-10-13 12:33:22 -05:00
980024dfec Updated user function 2023-10-13 09:34:13 -05:00
1804ec0c9e Updated user function 2023-10-13 09:30:05 -05:00
8a2556b773 Added some textures. 2023-10-13 09:15:32 -05:00
1c64569d9a Added some textures. 2023-10-12 14:01:09 -05:00
ed26d36941 Added some textures. 2023-10-12 10:59:33 -05:00
97e58928ed Added Pouchdb persistance manager. 2023-10-12 10:58:50 -05:00
6630e52ba4 Fixed mobile scene aspect ratios. 2023-09-27 08:31:01 -05:00
4f71fcefbd Cleaned up intro/demo. 2023-09-25 13:59:59 -05:00
3580cf2559 refactored new relic integration. 2023-09-25 13:20:53 -05:00
5150a017bf Added soccer field to edit menu and removed from main app. 2023-09-25 12:27:40 -05:00
296 changed files with 32350 additions and 7109 deletions

75
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,75 @@
name: Build and Deploy
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: linux_amd64
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install dependencies
run: npm ci
timeout-minutes: 5
- name: Build Front End
run: npm run build
timeout-minutes: 10
env:
NODE_OPTIONS: '--max-old-space-size=4096'
VITE_AUTH0_CLIENTID: ${{ secrets.VITE_AUTH0_CLIENTID }}
VITE_AUTH0_DOMAIN: ${{ secrets.VITE_AUTH0_DOMAIN }}
- name: Stop Service
run: |
sudo rc-service immersive stop || true
- name: Deploy to /opt/immersive
run: |
# Ensure group write so we can delete old files
sudo chmod -R g+w /opt/immersive || true
# Remove old files except data directory and env file
find /opt/immersive -mindepth 1 -maxdepth 1 ! -name 'data' ! -name '.env.production' -exec rm -rf {} +
# Copy built files to target
cp -r . /opt/immersive/
# Remove unnecessary directories
rm -rf /opt/immersive/.git /opt/immersive/.github
# Set permissions on start.sh and ensure group write for future deploys
chmod +x /opt/immersive/start.sh
sudo chmod -R g+w /opt/immersive
# Set ownership to immersive user
sudo chown -R immersive:immersive /opt/immersive
- name: Create Environment File
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
NEW_RELIC_LICENSE_KEY: ${{ secrets.NEW_RELIC_LICENSE_KEY }}
run: |
# Create .env.production with secrets (only accessible by immersive user)
echo "# Auto-generated by CI/CD - Do not edit manually" > /opt/immersive/.env.production
echo "ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}" >> /opt/immersive/.env.production
echo "CLOUDFLARE_ACCOUNT_ID=${CLOUDFLARE_ACCOUNT_ID}" >> /opt/immersive/.env.production
echo "CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN}" >> /opt/immersive/.env.production
echo "NEW_RELIC_LICENSE_KEY=${NEW_RELIC_LICENSE_KEY}" >> /opt/immersive/.env.production
# Secure the environment file
sudo chown immersive:immersive /opt/immersive/.env.production
sudo chmod 600 /opt/immersive/.env.production
- name: Start Service
run: |
sudo rc-service immersive start

19
.github/workflows/main.yml vendored Normal file
View File

@ -0,0 +1,19 @@
name: Node.js CI
on:
push:
branches: [ "deepdiagram" ]
pull_request:
branches: [ "deepdiagram" ]
jobs:
build:
runs-on: self-hosted
steps:
- uses: actions/checkout@v2
- name: Use Node.js 20.x
uses: actions/setup-node@v2
with:
node-version: 20.x
- run: cp -r ./dist/* /var/www/deepdiagram

50
.github/workflows/node.js.yml vendored Normal file
View File

@ -0,0 +1,50 @@
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
name: Node.js Github Side
on:
push:
branches: [ "deepdiagram" ]
pull_request:
branches: [ "deepdiagram" ]
jobs:
build:
name: build app
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 20.x
uses: actions/setup-node@v2
with:
node-version: 20.x
cache-dependency-path: package-lock.json
- run: echo "test"
- run: npm ci
- run: npm run build
env:
VITE_SYNCDB_ENDPOINT: ${{ secrets.VITE_SYNCDB_ENDPOINT }}
VITE_USER_ENDPOINT: ${{ secrets.VITE_USER_ENDPOINT }}
VITE_CREATE_ENDPOINT: ${{ secrets.VITE_CREATE_ENDPOINT }}
NODE_OPTIONS: --max-old-space-size=4096
- name: Archive production artifacts
uses: actions/upload-artifact@v4
with:
name: upload-dist
path: ./dist
overwrite: true
deploy:
name: deploy on linode
needs: build
runs-on: self-hosted
steps:
- name: get artifact
uses: actions/download-artifact@v4
with:
name: upload-dist
path: dist
- run: pwd
- run: cp -r ./dist/* /var/www/deepdiagram/

2
.gitignore vendored
View File

@ -25,3 +25,5 @@ dist-ssr
# Local Netlify folder # Local Netlify folder
.netlify .netlify
/data/
/.env.production

280
ALPINE_SERVICE.md Normal file
View File

@ -0,0 +1,280 @@
# Alpine Linux Service Setup
This guide covers installing and running Immersive as a service on Alpine Linux using OpenRC.
## Prerequisites
```bash
# Update packages
apk update
# Install Node.js 18+ and npm
apk add nodejs npm
# Install build dependencies (required for native modules like leveldown)
apk add python3 make g++ git
# Verify Node version (must be >= 18)
node --version
```
## Create Service User
Create a dedicated user to run the service (security best practice):
```bash
# Create immersive group and user (no login shell, no home directory)
addgroup -S immersive
adduser -S -G immersive -H -s /sbin/nologin immersive
# Create directories with proper ownership
mkdir -p /opt/immersive
mkdir -p /var/log/immersive
mkdir -p /var/run/immersive
chown -R immersive:immersive /opt/immersive
chown -R immersive:immersive /var/log/immersive
chown -R immersive:immersive /var/run/immersive
```
## Installation
```bash
# Create application directory
mkdir -p /opt/immersive
cd /opt/immersive
# Clone or copy the application
git clone <your-repo-url> .
# OR copy files manually
# Install dependencies
npm ci --production=false
# Build the application
NODE_OPTIONS='--max-old-space-size=4096' npm run build
# Copy Havok physics WASM (if not already done by build)
npm run havok
# Create data directory for PouchDB
mkdir -p /opt/immersive/data
# Set ownership to immersive user
chown -R immersive:immersive /opt/immersive
```
## Start Script
The `start.sh` script is included in the repository. After deployment, ensure it's executable:
```bash
chmod +x /opt/immersive/start.sh
```
The script sets up the environment and starts the Node.js server, logging output to `/var/log/immersive/`.
## OpenRC Service
Create `/etc/init.d/immersive`:
```bash
#!/sbin/openrc-run
name="immersive"
description="Immersive WebXR Diagramming Application"
command="/opt/immersive/start.sh"
command_user="immersive:immersive"
command_background="yes"
pidfile="/var/run/immersive/immersive.pid"
directory="/opt/immersive"
output_log="/var/log/immersive/app.log"
error_log="/var/log/immersive/error.log"
depend() {
need net
after firewall
}
start_pre() {
checkpath --directory --owner immersive:immersive --mode 0755 /var/log/immersive
checkpath --directory --owner immersive:immersive --mode 0755 /var/run/immersive
checkpath --file --owner immersive:immersive --mode 0644 /var/log/immersive/app.log
checkpath --file --owner immersive:immersive --mode 0644 /var/log/immersive/error.log
}
```
Make it executable and enable:
```bash
chmod +x /etc/init.d/immersive
rc-update add immersive default
```
## Service Management
```bash
# Start the service
rc-service immersive start
# Stop the service
rc-service immersive stop
# Restart the service
rc-service immersive restart
# Check status
rc-service immersive status
# View logs
tail -f /var/log/immersive/app.log
tail -f /var/log/immersive/error.log
```
## Log Rotation
Create `/etc/logrotate.d/immersive`:
```
/var/log/immersive/*.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
create 0644 immersive immersive
postrotate
rc-service immersive restart > /dev/null 2>&1 || true
endscript
}
```
Install logrotate if not present:
```bash
apk add logrotate
```
## Environment Variables
Create `/opt/immersive/.env.production` for production settings:
```bash
# Server
NODE_ENV=production
PORT=3001
# Auth0 (if using authentication)
# VITE_AUTH0_DOMAIN=your-domain.auth0.com
# VITE_AUTH0_CLIENT_ID=your-client-id
# Database sync endpoint (optional)
# VITE_SYNCDB_ENDPOINT=https://your-couchdb-server.com
```
## Firewall (if using iptables)
```bash
# Allow port 3001
iptables -A INPUT -p tcp --dport 3001 -j ACCEPT
# Save rules
rc-service iptables save
```
## Reverse Proxy (Optional)
If using nginx as a reverse proxy:
```bash
apk add nginx
```
Create `/etc/nginx/http.d/immersive.conf`:
```nginx
server {
listen 80;
server_name your-domain.com;
location / {
proxy_pass http://127.0.0.1:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
```
Enable and start nginx:
```bash
rc-update add nginx default
rc-service nginx start
```
## Gitea CI/CD Runner (Optional)
If using a Gitea Actions runner to deploy, grant the runner user write access to `/opt/immersive`:
```bash
# Add gitea-runner to immersive group
adduser gitea-runner immersive
# Set group write permissions on /opt/immersive
chmod -R g+w /opt/immersive
# Ensure new files inherit group ownership
chmod g+s /opt/immersive
# Allow runner to manage the service
# Add to /etc/sudoers.d/gitea-runner:
echo 'gitea-runner ALL=(ALL) NOPASSWD: /sbin/rc-service immersive *' > /etc/sudoers.d/gitea-runner
echo 'gitea-runner ALL=(ALL) NOPASSWD: /bin/chown -R immersive\:immersive /opt/immersive' >> /etc/sudoers.d/gitea-runner
chmod 440 /etc/sudoers.d/gitea-runner
```
The GitHub Actions workflow in `.github/workflows/build.yml` will handle deployment automatically on push to main.
## Troubleshooting
**Service fails to start:**
```bash
# Check logs
cat /var/log/immersive/error.log
# Run manually as immersive user to see errors
su -s /bin/sh immersive -c "cd /opt/immersive && NODE_ENV=production node server.js"
```
**Native module errors (leveldown):**
```bash
# Rebuild native modules
cd /opt/immersive
npm rebuild leveldown
```
**Permission issues:**
```bash
# Ensure proper ownership (must be immersive user)
chown -R immersive:immersive /opt/immersive
chown -R immersive:immersive /var/log/immersive
chown -R immersive:immersive /var/run/immersive
chmod -R 755 /opt/immersive
```
**Port already in use:**
```bash
# Find process using port 3001
lsof -i :3001
# Or
netstat -tlnp | grep 3001
```

137
CLAUDE.md Normal file
View File

@ -0,0 +1,137 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
This is "immersive" - a WebXR/VR diagramming application built with BabylonJS and React. It allows users to create and interact with 3D diagrams in both standard web browsers and VR environments, with real-time collaboration via PouchDB sync.
## Build and Development Commands
### Development
- `npm run dev` - Start Vite dev server on port 3001 (DO NOT USE per user instructions)
- `npm run build` - Build production bundle (includes version bump)
- `npm run preview` - Preview production build on port 3001
- `npm test` - Run tests with Vitest
- `npm run socket` - Start WebSocket server for collaboration (port 8080)
- `npm run serverBuild` - Compile TypeScript server code
- `npm run havok` - Copy Havok physics WASM files to Vite deps
### Testing
- Run all tests: `npm test`
- No single test command is configured; tests use Vitest
## Architecture
### Core Technologies
- **BabylonJS 8.x**: 3D engine with WebXR support and Havok physics
- **React + Mantine**: UI framework for 2D interface and settings
- **PouchDB**: Client-side database with CouchDB sync for collaboration
- **Auth0**: Authentication provider
- **Vite**: Build tool and dev server
### Key Architecture Patterns
#### Singleton Scene Management
The application uses a singleton pattern for the BabylonJS Scene via `DefaultScene` (src/defaultScene.ts). Always access the scene through `DefaultScene.Scene` rather than creating new instances.
#### Observable-Based Event System
The application heavily uses BabylonJS Observables for event handling:
- **DiagramManager.onDiagramEventObservable**: Central hub for diagram entity changes
- **DiagramManager.onUserEventObservable**: User position/state updates for multiplayer
- **AppConfig.onConfigChangedObservable**: Application settings changes
- **controllerObservable**: VR controller input events
Event observers use a mask system (`DiagramEventObserverMask`) to distinguish:
- `FROM_DB`: Events coming from database sync (shouldn't trigger database writes)
- `TO_DB`: Events that should be persisted to database
#### Diagram Entity System
All 3D objects in the scene are represented by `DiagramEntity` types (src/diagram/types/diagramEntity.ts):
- Entities have a template reference (e.g., `#image-template`)
- Managed by `DiagramManager` which maintains a Map of `DiagramObject` instances
- Changes propagate through the Observable system to database and other clients
#### VR Controller Architecture
Controllers inherit from `AbstractController` with specialized implementations:
- `LeftController`: Menu interactions, navigation
- `RightController`: Object manipulation, selection
- Controllers communicate via `controllerObservable` with `ControllerEvent` messages
- `Rigplatform` manages the player rig and handles locomotion
#### Database & Sync
- `PouchdbPersistenceManager` (src/integration/database/pouchdbPersistenceManager.ts) handles all persistence
- Supports optional encryption via `Encryption` class
- Syncs to remote CouchDB via proxy (configured in vite.config.ts)
- URL pattern `/db/public/:db` or `/db/private/:db` determines database name
- Uses `presence.ts` for broadcasting user positions over WebSocket
### Project Structure
- `src/vrcore/`: Engine initialization and core VR setup
- `src/controllers/`: VR controller implementations and input handling
- `src/diagram/`: 3D diagram entities, management, and scene interaction
- `src/integration/`: Database sync, encryption, and presence system
- `src/menus/`: In-VR 3D menus (not React components)
- `src/objects/`: Reusable 3D objects (buttons, handles, avatars)
- `src/react/`: React UI components for 2D interface
- `src/util/`: Shared utilities and configuration
- `server/`: WebSocket server for real-time presence
### Configuration System
Two configuration systems exist (being migrated):
1. **AppConfig class** (src/util/appConfig.ts): Observable-based config with typed properties
2. **ConfigType** (bottom of appConfig.ts): Legacy localStorage-based config
Settings include snapping values, physics toggles, fly mode, and turn snap angles.
## Important Development Notes
### Proxy Configuration
The dev and preview servers proxy certain routes to production:
- `/sync/*` - Database sync endpoint
- `/create-db` - Database creation
- `/api/images` - Image uploads
### Physics System
- Uses Havok physics engine (requires WASM file via `npm run havok`)
- Physics can be enabled/disabled via AppConfig
- `customPhysics.ts` provides helper functions
### WebGPU Support
The engine initializer supports both WebGL and WebGPU backends via the `useWebGpu` parameter.
### Encryption
Databases can be optionally encrypted. The `Encryption` class handles AES encryption with password-derived keys. Salt is stored in metadata document.
### Environment Variables
- `VITE_USER_ENDPOINT`: User authentication endpoint
- `VITE_SYNCDB_ENDPOINT`: Remote database sync endpoint
Check `.env.local` for local configuration.
## Naming Conventions
### Tool and Material Naming
**Material Names:** Materials follow the pattern `material-{color}` where `{color}` is the hex color string (e.g., `material-#ff0000` for red).
**Tool Mesh Names:** Tools use the pattern `tool-{toolType}-{color}`:
- Example: `tool-BOX-#ff0000` (red box tool)
- ToolTypes: `BOX`, `SPHERE`, `CYLINDER`, `CONE`, `PLANE`, `PERSON`
**Tool Instance Names:** `tool-instance-{toolType}-{color}` (e.g., `tool-instance-BOX-#ff0000`)
**Implementation details:**
- 16 predefined toolbox colors (see docs/NAMING_CONVENTIONS.md)
- Materials created in `src/toolbox/functions/buildColor.ts`
- Tool meshes created in `src/toolbox/functions/buildTool.ts`
- When extracting colors from materials, use: `emissiveColor || diffuseColor` (priority order)
### Rendering Modes
Three rendering modes affect material properties:
1. **Lightmap with Lighting**: Uses `diffuseColor` + `lightmapTexture` (expensive)
2. **Unlit with Emissive Texture** (default): Uses `emissiveColor` + `emissiveTexture` (lightmap)
3. **Flat Emissive**: Uses only `emissiveColor` (fastest)
See `src/util/renderingMode.ts` and `src/util/lightmapGenerator.ts` for implementation.

403
EXPRESS_API_PLAN.md Normal file
View File

@ -0,0 +1,403 @@
# Express.js API Server Plan
## Goal
Add an Express.js backend server to handle API routes (starting with Claude API), with support for either combined or split deployment.
## Advantages Over Next.js Migration
- **Minimal frontend changes** - only API URL configuration
- **No routing changes** - keep react-router-dom as-is
- **Flexible deployment** - combined or split frontend/backend
- **Already partially exists** - `server.js` in root has Express + vite-express scaffolding
## Deployment Options
### Option A: Combined (Single Server)
```
Express Server (vite-express)
├── Serves static files from dist/
└── Handles /api/* routes
```
- Simpler setup, one deployment
- Good for: VPS, Railway, Fly.io, DigitalOcean App Platform
### Option B: Split (Separate Hosts)
```
Static Host (CDN) API Server (Node.js)
├── Cloudflare Pages ├── Railway
├── Netlify ├── Fly.io
├── Vercel ├── AWS Lambda
└── S3 + CloudFront └── Any VPS
Serves dist/ Handles /api/*
```
- Better scalability, cheaper static hosting
- Good for: High traffic, global CDN distribution
---
## Current State
### Existing `server.js` (incomplete)
```javascript
import express from "express";
import ViteExpress from "vite-express";
import dotenv from "dotenv";
import expressProxy from "express-http-proxy";
dotenv.config();
const app = express();
app.use("/api", expressProxy("local.immersiveidea.com"));
ViteExpress.listen(app, process.env.PORT || 3001, () => console.log("Server is listening..."));
```
### Missing Dependencies
The following packages are imported but not in package.json:
- `express`
- `vite-express`
- `express-http-proxy`
- `dotenv`
---
## Implementation Plan
### Phase 1: Install Dependencies
```bash
npm install express vite-express dotenv cors
```
- `express` - Web framework
- `vite-express` - Vite integration for combined deployment
- `dotenv` - Environment variable loading
- `cors` - Cross-origin support for split deployment
### Phase 2: Create API Routes Structure
Create a modular API structure:
```
server/
├── server.js # Existing WebSocket server (keep as-is)
├── api/
│ ├── index.js # Main API router
│ └── claude.js # Claude API proxy route
```
### Phase 3: Update Root `server.js`
Replace the current incomplete server.js with:
```javascript
import express from "express";
import ViteExpress from "vite-express";
import cors from "cors";
import dotenv from "dotenv";
import apiRoutes from "./server/api/index.js";
dotenv.config();
const app = express();
// CORS configuration for split deployment
// In combined mode, same-origin requests don't need CORS
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(",") || [];
if (allowedOrigins.length > 0) {
app.use(cors({
origin: allowedOrigins,
credentials: true,
}));
}
app.use(express.json());
// API routes
app.use("/api", apiRoutes);
// Check if running in API-only mode (split deployment)
const apiOnly = process.env.API_ONLY === "true";
if (apiOnly) {
// API-only mode: no static file serving
app.listen(process.env.PORT || 3000, () => {
console.log(`API server running on port ${process.env.PORT || 3000}`);
});
} else {
// Combined mode: Vite handles static files + SPA
ViteExpress.listen(app, process.env.PORT || 3001, () => {
console.log(`Server running on port ${process.env.PORT || 3001}`);
});
}
```
### Phase 4: Create API Router
**`server/api/index.js`**:
```javascript
import { Router } from "express";
import claudeRouter from "./claude.js";
const router = Router();
// Claude API proxy
router.use("/claude", claudeRouter);
// Health check
router.get("/health", (req, res) => {
res.json({ status: "ok" });
});
export default router;
```
**`server/api/claude.js`**:
```javascript
import { Router } from "express";
const router = Router();
const ANTHROPIC_API_URL = "https://api.anthropic.com";
router.post("/*", async (req, res) => {
const apiKey = process.env.ANTHROPIC_API_KEY;
if (!apiKey) {
return res.status(500).json({ error: "API key not configured" });
}
// Get the path after /api/claude (e.g., /v1/messages)
const path = req.params[0] || req.path;
try {
const response = await fetch(`${ANTHROPIC_API_URL}${path}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": apiKey,
"anthropic-version": "2023-06-01",
},
body: JSON.stringify(req.body),
});
const data = await response.json();
res.status(response.status).json(data);
} catch (error) {
console.error("Claude API error:", error);
res.status(500).json({ error: "Failed to proxy request to Claude API" });
}
});
export default router;
```
### Phase 5: Update Vite Config
Remove the Claude proxy from `vite.config.ts` since Express handles it now.
**Before** (lines 41-56):
```javascript
'^/api/claude': {
target: 'https://api.anthropic.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/claude/, ''),
configure: (proxy) => {
proxy.on('proxyReq', (proxyReq) => {
const apiKey = env.ANTHROPIC_API_KEY;
// ...
});
}
}
```
**After**: Remove this block entirely. The Express server handles `/api/claude/*`.
Keep the other proxies (`/sync/*`, `/create-db`, `/api/images`) - they still proxy to deepdiagram.com in dev mode.
### Phase 6: Add API URL Configuration (for Split Deployment)
Create a utility to get the API base URL:
**`src/util/apiConfig.ts`**:
```typescript
// API base URL - empty string for same-origin (combined deployment)
// Set VITE_API_URL for split deployment (e.g., "https://api.yourdomain.com")
export const API_BASE_URL = import.meta.env.VITE_API_URL || '';
export function apiUrl(path: string): string {
return `${API_BASE_URL}${path}`;
}
```
**Update `src/react/services/diagramAI.ts`**:
```typescript
import { apiUrl } from '../../util/apiConfig';
// Change from:
const response = await fetch('/api/claude/v1/messages', { ... });
// To:
const response = await fetch(apiUrl('/api/claude/v1/messages'), { ... });
```
This change is backward-compatible:
- **Combined deployment**: `VITE_API_URL` is empty, calls go to same origin
- **Split deployment**: `VITE_API_URL=https://api.example.com`, calls go to API server
### Phase 7: Update package.json Scripts
```json
"scripts": {
"dev": "node server.js",
"build": "node versionBump.js && vite build",
"start": "NODE_ENV=production node server.js",
"start:api": "API_ONLY=true node server.js",
"test": "vitest",
"socket": "node server/server.js",
"serverBuild": "cd server && tsc"
}
```
**Changes:**
- `dev`: Runs Express + vite-express (serves Vite in dev mode)
- `start`: Combined mode - serves dist/ + API
- `start:api`: API-only mode for split deployment
- Removed `preview` (use `start` instead)
---
## File Changes Summary
| Action | File | Description |
|--------|------|-------------|
| Modify | `package.json` | Add dependencies, update scripts |
| Modify | `server.js` | Full Express server with CORS + API routes |
| Create | `server/api/index.js` | Main API router |
| Create | `server/api/claude.js` | Claude API proxy endpoint |
| Create | `src/util/apiConfig.ts` | API URL configuration utility |
| Modify | `src/react/services/diagramAI.ts` | Use apiUrl() for API calls |
| Modify | `vite.config.ts` | Remove `/api/claude` proxy block |
---
## How vite-express Works
`vite-express` is a simple integration that:
1. **Development**: Runs Vite's dev server as middleware, providing HMR
2. **Production**: Serves the built `dist/` folder as static files
This means:
- One server handles both API and frontend
- No CORS issues (same origin)
- HMR works in development
- Production-ready with `vite build`
---
## Production Deployment
### Option A: Combined Deployment
Single server handles both frontend and API:
```bash
# Build frontend
npm run build
# Start combined server (serves dist/ + API)
npm run start
```
**Environment variables (.env)**:
```bash
PORT=3001
ANTHROPIC_API_KEY=sk-ant-...
```
The Express server will:
1. Handle `/api/*` routes directly
2. Serve static files from `dist/`
3. Fall back to `dist/index.html` for SPA routing
### Option B: Split Deployment
Separate hosting for frontend (CDN) and API (Node server):
**API Server:**
```bash
# Start API-only server
npm run start:api
```
**Environment variables (.env for API server)**:
```bash
PORT=3000
API_ONLY=true
ANTHROPIC_API_KEY=sk-ant-...
ALLOWED_ORIGINS=https://your-frontend.com,https://www.your-frontend.com
```
**Frontend (Static Host):**
```bash
# Build with API URL configured
VITE_API_URL=https://api.yourdomain.com npm run build
# Deploy dist/ to your static host (Cloudflare Pages, Netlify, etc.)
```
**Environment variables (.env.production for frontend build)**:
```bash
VITE_API_URL=https://api.yourdomain.com
```
### Deployment Examples
| Deployment | Frontend | API Server | Cost |
|------------|----------|------------|------|
| Combined | Railway | (same) | ~$5/mo |
| Combined | Fly.io | (same) | Free tier |
| Split | Cloudflare Pages (free) | Railway ($5/mo) | ~$5/mo |
| Split | Netlify (free) | Fly.io (free) | Free |
| Split | Vercel (free) | AWS Lambda | Pay-per-use |
---
## Future API Routes
To add more API routes, create new files in `server/api/`:
```javascript
// server/api/index.js
import claudeRouter from "./claude.js";
import imagesRouter from "./images.js"; // future
import authRouter from "./auth.js"; // future
router.use("/claude", claudeRouter);
router.use("/images", imagesRouter);
router.use("/auth", authRouter);
```
---
## Migration Order
1. `npm install express vite-express dotenv cors`
2. Create `server/api/index.js`
3. Create `server/api/claude.js`
4. Create `src/util/apiConfig.ts`
5. Update `src/react/services/diagramAI.ts` to use `apiUrl()`
6. Update `server.js` (root) with full Express + CORS setup
7. Remove `/api/claude` proxy from `vite.config.ts`
8. Update `package.json` scripts
9. Test combined: `npm run dev` and verify Claude API works
10. (Optional) Test split: Set `VITE_API_URL` and `API_ONLY=true`
---
## Notes
- **WebSocket server unchanged**: `server/server.js` (port 8080) runs separately
- **Minimal frontend changes**: Only `diagramAI.ts` updated to use `apiUrl()`
- **Environment variables**: `ANTHROPIC_API_KEY` already in `.env.local`
- **Node version**: Requires Node 18+ for native `fetch`
- **CORS**: Only enabled when `ALLOWED_ORIGINS` is set (split deployment)
- **Backward compatible**: Works as combined deployment by default

30
LICENSE.txt Normal file
View File

@ -0,0 +1,30 @@
Permissions Conditions Limitations
Commercial use
Distribution
Modification
Private use
License and copyright notice
Liability
Warranty
MIT License
Copyright (c) [2024] [Michael Mainguy]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

167
NEXT_MIGRATION_PLAN.md Normal file
View File

@ -0,0 +1,167 @@
# Vite to Next.js Migration Plan
## Goal
Migrate from Vite to Next.js App Router to get proper API route support, with minimal changes to existing code.
## Configuration
- **Router**: App Router with `'use client'` on all pages
- **Rendering**: CSR only (no SSR) - simplifies migration since BabylonJS can't SSR
- **API Routes**: Claude API now, structured for future expansion
- **External Proxies**: Keep sync/create-db/images as Next.js rewrites to deepdiagram.com
---
## Phase 1: Setup (No Breaking Changes)
### 1.1 Install Next.js
```bash
npm install next
```
### 1.2 Create `next.config.js`
```javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
async rewrites() {
return [
{ source: '/sync/:path*', destination: 'https://www.deepdiagram.com/sync/:path*' },
{ source: '/create-db', destination: 'https://www.deepdiagram.com/create-db' },
{ source: '/api/images', destination: 'https://www.deepdiagram.com/api/images' },
];
},
webpack: (config, { isServer }) => {
config.experiments = { ...config.experiments, asyncWebAssembly: true };
return config;
},
};
module.exports = nextConfig;
```
### 1.3 Update `tsconfig.json`
Add path alias:
```json
"baseUrl": ".",
"paths": { "@/*": ["./*"] }
```
---
## Phase 2: Create New Files
### 2.1 `src/react/providers.tsx` (extract from webApp.tsx)
- Move Auth0Provider and FeatureProvider wrapping here
- Add `'use client'` directive
- Handle window/document checks for SSR safety
### 2.2 `app/layout.tsx`
- Root layout with html/body tags
- Metadata (title, favicon from current index.html)
- Import global CSS
### 2.3 `app/globals.css`
```css
@import '../src/react/styles.css';
@import '@mantine/core/styles.css';
```
### 2.4 `app/api/claude/[...path]/route.ts`
- POST handler that proxies to api.anthropic.com
- Injects `ANTHROPIC_API_KEY` from env
- Adds `x-api-key` and `anthropic-version` headers
### 2.5 Page files (all with `'use client'`)
| Route | File | Component |
|-------|------|-----------|
| `/` | `app/page.tsx` | About |
| `/documentation` | `app/documentation/page.tsx` | Documentation |
| `/examples` | `app/examples/page.tsx` | Examples |
| `/pricing` | `app/pricing/page.tsx` | Pricing |
| `/db/[visibility]/[db]` | `app/db/[visibility]/[db]/page.tsx` | VrExperience |
| 404 | `app/not-found.tsx` | NotFound |
### 2.6 `src/react/components/ProtectedPage.tsx`
- Next.js version of route protection
- Uses `useRouter` from `next/navigation` for redirects
---
## Phase 3: Modify Existing Files
### 3.1 `src/react/pages/vrExperience.tsx`
**Changes:**
- Remove `useParams()` from react-router-dom
- Accept `visibility` and `db` as props instead
- Replace `useNavigate()` with `useRouter()` from `next/navigation`
### 3.2 `src/react/pageHeader.tsx`
**Changes:**
- Replace `import {Link} from "react-router-dom"` with `import Link from "next/link"`
- Change `to={item.href}` to `href={item.href}` on Link components
### 3.3 `src/react/marketing/about.tsx`
**Changes:**
- Replace `useNavigate()` with `useRouter()` from `next/navigation`
- Change `navigate('/path')` to `router.push('/path')`
### 3.4 `package.json`
```json
"scripts": {
"dev": "next dev -p 3001",
"build": "node versionBump.js && next build",
"start": "next start -p 3001",
"test": "vitest",
"socket": "node server/server.js",
"serverBuild": "cd server && tsc"
}
```
---
## Phase 4: Delete Old Files
| File | Reason |
|------|--------|
| `vite.config.ts` | Replaced by next.config.js |
| `index.html` | Next.js generates HTML |
| `src/webApp.ts` | Entry point no longer needed |
| `src/react/webRouter.tsx` | Replaced by app/ routing |
| `src/react/webApp.tsx` | Logic moved to providers.tsx |
| `src/react/components/ProtectedRoute.tsx` | Replaced by ProtectedPage.tsx |
---
## Critical Files to Modify
- `src/react/pages/vrExperience.tsx` - useParams -> props
- `src/react/pageHeader.tsx` - react-router Link -> Next.js Link
- `src/react/marketing/about.tsx` - useNavigate -> useRouter
- `src/react/webApp.tsx` - extract to providers.tsx
- `package.json` - scripts update
- `tsconfig.json` - path aliases
---
## Migration Order
1. Install next, create next.config.js
2. Update tsconfig.json
3. Create app/globals.css
4. Create src/react/providers.tsx
5. Create app/layout.tsx
6. Create app/api/claude/[...path]/route.ts
7. Create src/react/components/ProtectedPage.tsx
8. Modify vrExperience.tsx (accept props)
9. Create all app/*/page.tsx files
10. Modify pageHeader.tsx (Next.js Link)
11. Modify about.tsx (useRouter)
12. Update package.json scripts
13. Delete old files (vite.config.ts, index.html, webApp.ts, webRouter.tsx, webApp.tsx)
14. Test all routes
---
## Notes
- **Havok WASM**: Move `HavokPhysics.wasm` to `public/` folder
- **react-router-dom**: Can be removed from dependencies after migration
- **vite devDependencies**: Can be removed (vite, vite-plugin-cp)

224
ROADMAP.md Normal file
View File

@ -0,0 +1,224 @@
# Immersive - Product Roadmap
## Vision
Transform immersive into an accessible, intuitive WebXR diagramming platform that delivers a frictionless onboarding experience and sustainable growth path.
---
## Phase 1: Onboarding & User Experience (Q1 2025)
### 1.1 Frictionless Entry
**Goal:** Reduce barriers to entry for new users
- [ ] Redesign landing page to clearly guide users to immersive experience
- [ ] Create one-click "Enter VR" / "Try Demo" workflow
- [ ] Optimize initial load time and progressive loading
- [ ] Add clear device compatibility messaging (desktop/VR)
- [ ] Implement guest mode with no sign-in required for basic exploration
### 1.2 Marketing Content
**Goal:** Communicate value proposition effectively
- [ ] Create 3-5 demo videos showcasing key features (30-60 seconds each)
- Creating a basic diagram
- VR interaction showcase
- Collaboration features
- Template usage
- [ ] Develop tutorial video (2-3 minutes) explaining core workflows
- [ ] Autoplay video carousel on landing page
- [ ] Write marketing copy for landing page
- Hero section with clear value proposition
- Feature highlights
- Use case examples
- Call-to-action
### 1.3 In-Experience Tutorial
**Goal:** Replace external tutorial with immersive learning
- [ ] Remove existing external tutorial system
- [ ] Design in-VR tutorial experience with interactive steps
- [ ] Implement progressive disclosure (teach as users interact)
- [ ] Add contextual tooltips and hints in 3D space
- [ ] Create "first-time user" detection and guided walkthrough
- [ ] Add skip/replay tutorial options
### 1.4 Template System
**Goal:** Provide starting points for new users
- [ ] Design template/example diagram system
- [ ] Create 5-10 starter templates:
- Simple organizational chart
- Project workflow diagram
- Concept mapping example
- Architecture diagram
- Spatial layout example
- [ ] Build template browser UI (2D and VR)
- [ ] Implement "New from Template" workflow
- [ ] Add template preview/thumbnail generation
---
## Phase 2: Collaboration & Sync (Q2 2025)
### 2.1 Cross-Device Sharing
**Goal:** Enable seamless content sharing between desktop and Quest
- [ ] Research device-to-device sync options (WebRTC, local network)
- [ ] Design sync architecture without backend dependency
- [ ] Implement user content sync for signed-in users
- [ ] Add fallback to server-based sync when needed
- [ ] Create device pairing UI/workflow
- [ ] Test sync reliability across desktop ↔ Quest
- [ ] Add conflict resolution for simultaneous edits
---
## Phase 3: Immersion & Environment (Q2-Q3 2025)
### 3.1 Audio Integration
**Goal:** Enhance presence with ambient soundscapes
- [ ] Source/create ambient audio assets
- Nature sounds (birds, wind, water)
- Office ambience
- Abstract/focus music
- [ ] Implement spatial audio system
- [ ] Add audio settings (volume, on/off, environment selection)
- [ ] Create audio manager for seamless transitions
- [ ] Add positional audio for collaboration (optional user voices)
### 3.2 Environment System
**Goal:** Provide varied immersive environments
- [ ] Design environment switching architecture
- [ ] Create environment presets:
- Outdoor/nature scene
- Modern office
- Abstract/minimal space
- Workshop/studio
- [ ] Implement skybox and lighting variations
- [ ] Build environment selector UI (2D and VR)
- [ ] Optimize environment assets for performance
- [ ] Add environment-specific audio pairing
---
## Phase 4: User Feedback & Polish (Q3 2025)
### 4.1 In-VR Feedback Mechanism
**Goal:** Enable users to provide feedback without leaving VR
- [ ] Design in-VR feedback form/interface
- [ ] Implement voice-to-text option (VR accessibility)
- [ ] Add screenshot/recording attachment capability
- [ ] Create feedback submission backend
- [ ] Build feedback review dashboard
- [ ] Add "Report Bug" quick action in VR menu
### 4.2 Keyboard Improvements
**Goal:** Improve text input experience
- [ ] Test system keyboard integration (Quest/desktop)
- [ ] Evaluate custom keyboard vs. native keyboard UX
- [ ] Implement system keyboard fallback where supported
- [ ] Optimize keyboard positioning in VR space
- [ ] Add keyboard shortcuts for power users (desktop)
---
## Phase 5: Growth & Monetization (Q4 2025)
### 5.1 Marketing Roadmap
**Goal:** Build sustainable user acquisition
- [ ] Define target audience segments
- Educators
- Remote teams
- Designers/architects
- Knowledge workers
- [ ] Create content marketing strategy
- Blog posts on use cases
- Social media showcase
- Community building (Discord/Reddit)
- [ ] Develop SEO optimization plan
- [ ] Plan partnership outreach (VR communities, productivity tools)
- [ ] Create referral/sharing incentives
- [ ] Build analytics dashboard for user metrics
### 5.2 Monetization Strategy
**Goal:** Establish path to sustainability
**Potential Revenue Streams:**
- [ ] Freemium model research
- Free tier: Limited diagrams, basic features
- Pro tier: Unlimited diagrams, advanced features, collaboration
- [ ] Team/Enterprise pricing
- Private deployment options
- Admin controls
- Priority support
- [ ] Template marketplace
- Premium templates
- Community submissions (revenue share)
- [ ] Educational licensing
- Institutional pricing
- Classroom management features
**Implementation:**
- [ ] Define pricing tiers and feature gates
- [ ] Integrate payment processing (Stripe)
- [ ] Build subscription management UI
- [ ] Implement feature flags for tier differentiation
- [ ] Create upgrade prompts and conversion flow
- [ ] Add usage analytics for pricing optimization
---
## Success Metrics
### Phase 1-2 (Onboarding)
- Time to first diagram creation < 2 minutes
- Tutorial completion rate > 60%
- Return user rate (7-day) > 30%
### Phase 3-4 (Engagement)
- Average session duration > 15 minutes
- User satisfaction score > 4/5
- Feedback submission rate (active users) > 10%
### Phase 5 (Growth)
- Monthly active users growth > 20% MoM
- Free-to-paid conversion rate > 5%
- Customer acquisition cost < lifetime value
---
## Technical Debt & Infrastructure
### Ongoing Priorities
- [ ] Migration from legacy ConfigType to AppConfig
- [ ] Performance optimization (target 90fps in VR)
- [ ] Accessibility improvements (WCAG compliance)
- [ ] Testing coverage > 70%
- [ ] Documentation for contributors
- [ ] CI/CD pipeline enhancements
---
## Notes
**Dependencies:**
- Auth0 for user authentication
- PouchDB/CouchDB for data persistence
- BabylonJS 8.x for rendering
- Vite for build tooling
**Platform Support:**
- Desktop browsers (Chrome, Firefox, Edge)
- Meta Quest 2/3/Pro
- Future: PSVR2, Vision Pro (evaluate demand)
**Review Cadence:** Quarterly roadmap review and adjustment based on user feedback and metrics.
---
*Last Updated: 2025-11-19*

150
SHARING_PLAN.md Normal file
View File

@ -0,0 +1,150 @@
# Self-Hosted Diagram Sharing with Express-PouchDB
## Requirements (Confirmed)
- **Storage**: In-memory (ephemeral) - lost on server restart
- **Content**: Copy current diagram entities when creating share
- **Expiration**: No expiration - links work until server restart
- **Encryption**: None - keep it simple, anyone with link can access
## Architecture
```
┌─────────────────────────────────────────────────┐
│ Express Server (port 3001) │
├─────────────────────────────────────────────────┤
│ /api/share/* → Share management API │
│ /pouchdb/* → express-pouchdb (sync) │
│ /share/:uuid → Client share route │
│ /api/* → Existing API routes │
│ /* → Vite static files │
└─────────────────────────────────────────────────┘
└── In-Memory PouchDB (per share UUID)
```
## Implementation Steps
### Phase 1: Server-Side Setup
#### 1.1 Add Dependencies
```bash
npm install express-pouchdb pouchdb-adapter-memory
```
#### 1.2 Create PouchDB Server Service
**New file: `server/services/pouchdbServer.js`**
- Initialize PouchDB with memory adapter
- Track active share databases in a Map
- Export `getShareDB(shareId)`, `shareExists(shareId)`, `createPouchDBMiddleware()`
#### 1.3 Create Share API
**New file: `server/api/share.js`**
- `POST /api/share/create` - Generate UUID, create in-memory DB, copy entities
- `GET /api/share/:id/exists` - Check if share exists
- `GET /api/share/stats` - Debug endpoint for active shares
#### 1.4 Update API Router
**Edit: `server/api/index.js`**
- Add `import shareRouter from "./share.js"`
- Mount at `router.use("/share", shareRouter)`
#### 1.5 Mount Express-PouchDB
**Edit: `server.js`**
- Import `createPouchDBMiddleware` from pouchdbServer.js
- Mount at `app.use("/pouchdb", createPouchDBMiddleware())`
---
### Phase 2: Client-Side Integration
#### 2.1 Update URL Parsing
**Edit: `src/util/functions/getPath.ts`**
- Add `getPathInfo()` function returning `{ dbName, isShare, shareId }`
- Detect `/share/:uuid` pattern
#### 2.2 Update PouchDB Persistence Manager
**Edit: `src/integration/database/pouchdbPersistenceManager.ts`**
In `initLocal()`:
- Call `getPathInfo()` to detect share URLs
- If share: use `share-{uuid}` as local DB name, call `beginShareSync()`
Add new method `beginShareSync(shareId)`:
- Check share exists via `/api/share/:id/exists`
- Connect to `${origin}/pouchdb/share-${shareId}`
- Set up presence with `share-${shareId}` as DB name
- Begin live sync (no encryption)
#### 2.3 Add React Route
**Edit: `src/react/webRouter.tsx`**
- Add route `{ path: "/share/:uuid", element: <VrExperience isShare={true} /> }`
- No ProtectedRoute wrapper (public access)
#### 2.4 Add Share Button Handler
**Edit: `src/react/pages/vrExperience.tsx`**
- Add `isShare` prop
- Add `handleShare()` function:
1. Get all entities from local PouchDB
2. POST to `/api/share/create` with entities
3. Copy resulting URL to clipboard
4. Show confirmation
---
### Phase 3: Presence Integration
The WebSocket presence system already routes by database name. Since shares use `share-{uuid}` as the database name, presence works automatically.
**Edit: `server/server.js`** (WebSocket server)
- Update `originIsAllowed()` to allow localhost for development
---
## Files to Modify
| File | Action | Purpose |
|------|--------|---------|
| `package.json` | Edit | Add express-pouchdb, pouchdb-adapter-memory |
| `server.js` | Edit | Mount /pouchdb middleware |
| `server/api/index.js` | Edit | Add share router |
| `server/services/pouchdbServer.js` | Create | PouchDB memory initialization |
| `server/api/share.js` | Create | Share API endpoints |
| `server/server.js` | Edit | Allow localhost origins |
| `src/util/functions/getPath.ts` | Edit | Add getPathInfo() |
| `src/integration/database/pouchdbPersistenceManager.ts` | Edit | Add share sync logic |
| `src/react/webRouter.tsx` | Edit | Add /share/:uuid route |
| `src/react/pages/vrExperience.tsx` | Edit | Add share button handler |
| `src/util/featureConfig.ts` | Edit | Enable shareCollaborate feature |
---
## User Flow
### Creating a Share
1. User has a diagram open at `/db/public/mydiagram`
2. Clicks "Share" button
3. Client fetches all entities from local PouchDB
4. POSTs to `/api/share/create` with entities
5. Server creates in-memory DB, copies entities, returns UUID
6. Client copies `https://server.com/share/{uuid}` to clipboard
7. User shares link with collaborators
### Joining a Share
1. User navigates to `https://server.com/share/{uuid}`
2. React Router renders VrExperience with `isShare=true`
3. PouchdbPersistenceManager detects share URL
4. Checks `/api/share/:uuid/exists` - returns true
5. Creates local PouchDB `share-{uuid}`
6. Connects to `/pouchdb/share-{uuid}` for sync
7. Entities replicate to local, render in scene
8. Presence WebSocket connects with `share-{uuid}` as room
---
## Future Authentication (Not Implemented Now)
Structure allows easy addition later:
- express-pouchdb middleware can be wrapped with auth middleware
- Share API can require JWT/session tokens
- Could add password-protected shares
- Could add read-only vs read-write permissions

179
SYNC_PLAN.md Normal file
View File

@ -0,0 +1,179 @@
# Future Sync Strategy: Keeping Local and Public Clones in Sync
## Current State (v1)
- Sharing creates a **ONE-TIME COPY** from local to public
- Copies diverge independently after sharing
- No automatic sync between local and public versions
- Local diagrams are browser-only (IndexedDB via PouchDB)
- Public diagrams sync with server via express-pouchdb
### URL Scheme
| Route | Sync | Access | Status |
|-------|------|--------|--------|
| `/db/local/:id` | None | Browser-only | Implemented |
| `/db/public/:id` | Yes | Anyone | Implemented |
| `/db/private/:id` | Yes | Authorized users | Route only (no auth) |
## Future Options
### Option 1: Manual Push/Pull (Recommended for v2)
Add explicit user-triggered sync between local and public copies.
**Features:**
- "Push to Public" button - sends local changes to public copy
- "Pull from Public" button - gets public changes into local
- Track `lastSyncedAt` timestamp
- Show indicator when copies have diverged
- Conflict resolution: Last write wins (simple) or user choice (advanced)
**Pros:**
- User stays in control
- Clear mental model
- Simple to implement incrementally
**Cons:**
- Manual effort required
- Risk of forgetting to sync
### Option 2: Automatic Background Sync
Continuous bidirectional sync between local and public copies.
**Features:**
- Real-time sync like Google Docs
- Works across devices
- Offline-first with automatic merge
**Pros:**
- Seamless experience
- Always up to date
**Cons:**
- Complex conflict resolution (may need CRDTs)
- Higher performance overhead
- Harder to reason about state
### Option 3: Fork/Branch Model
One-way relationship: local is "draft", public is "published".
**Features:**
- Push only (local → public)
- No pull mechanism
- Public is the "source of truth" once published
**Pros:**
- Clear mental model
- No merge conflicts
- Simple implementation
**Cons:**
- Cannot incorporate public changes back to local
- Multiple people can't collaborate on draft
## Recommended Implementation (v2)
Implement **Option 1 (Manual Push/Pull)** as it provides the best balance of user control and simplicity.
### Data Model Changes
Add to diagram directory entry:
```typescript
interface DiagramEntry {
_id: string;
name: string;
description: string;
storageType: 'local' | 'public' | 'private';
createdAt: string;
// New fields for sync tracking
publicCopyId?: string; // ID of the public clone (if shared)
lastPushedAt?: string; // When changes were last pushed to public
lastPulledAt?: string; // When public changes were last pulled
publicVersion?: number; // Version number of public copy at last sync
}
```
### API Endpoints
```typescript
// Push local changes to public
POST /api/sync/push
Body: { localDbName: string, publicDbName: string }
Response: { success: boolean, documentsUpdated: number }
// Pull public changes to local
POST /api/sync/pull
Body: { localDbName: string, publicDbName: string }
Response: { success: boolean, documentsUpdated: number }
// Check if copies have diverged
GET /api/sync/status?local={localDbName}&public={publicDbName}
Response: {
diverged: boolean,
localChanges: number,
publicChanges: number,
lastSyncedAt: string
}
```
### UI Components
1. **Sync Status Indicator**
- Shows in header when viewing a local diagram that has a public copy
- Green check: In sync
- Orange dot: Changes pending
- Red warning: Conflicts detected
2. **Push/Pull Buttons**
- In hamburger menu under "Share" section
- "Push to Public" - shows confirmation with change count
- "Pull from Public" - shows confirmation with change count
3. **Divergence Warning Badge**
- Shows on diagram card in Manage Diagrams modal
- Indicates when local and public have diverged
4. **Conflict Resolution Dialog**
- Shows when both local and public have changes to same entity
- Options: Keep Local, Keep Public, Keep Both (creates duplicate)
### Implementation Phases
**Phase 1: Tracking**
- Add `publicCopyId` when sharing local → public
- Track sharing relationship in directory
**Phase 2: Push**
- Implement push from local to public
- Overwrite public with local changes
- Update `lastPushedAt` timestamp
**Phase 3: Pull**
- Implement pull from public to local
- Merge public changes into local
- Update `lastPulledAt` timestamp
**Phase 4: Status**
- Implement divergence detection
- Add UI indicators
- Show sync status in Manage Diagrams
**Phase 5: Conflict Resolution**
- Detect entity-level conflicts
- Show resolution dialog
- Allow user to choose resolution strategy
## Migration Notes
Existing diagrams without `storageType` are treated as `public` for backwards compatibility. When such diagrams are loaded, the UI should work correctly but sync tracking features won't be available until the diagram metadata is updated.
## Security Considerations
- Push/pull operations should validate that the user has access to both databases
- Public databases remain world-readable/writable
- Private database sync will require authentication tokens
- Rate limiting should be applied to sync operations

297
VRCONFIGPLAN.md Normal file
View File

@ -0,0 +1,297 @@
# VR Configuration Panel Implementation Plan
## Overview
Create an immersive WebXR configuration panel that mirrors the 2D ConfigModal functionality using BabylonJS AdvancedDynamicTexture (ADT). The panel will allow users to adjust all application settings directly in VR.
## Recommended Approach: AdvancedDynamicTexture (ADT)
**Why ADT?**
- Most common approach for WebXR UI in BabylonJS
- Existing pattern in codebase (see `src/menus/configMenu.ts`)
- Good balance of simplicity and functionality
- Native support for text, buttons, sliders, and dropdowns
- Easy integration with existing Handle pattern
**Estimated Effort**: 150-200 lines of code, 4-8 hours implementation time
## File Structure
```
src/menus/
├── vrConfigPanel.ts (NEW - main implementation)
└── configMenu.ts (REFERENCE - existing VR config example)
src/diagram/
└── diagramMenuManager.ts (MODIFY - add toolbox button)
src/util/
└── appConfig.ts (USE - singleton for config management)
```
## Implementation Phases
### Phase 1: Core Panel Setup
- [ ] Create `src/menus/vrConfigPanel.ts` file
- [ ] Implement class structure following Handle pattern:
```typescript
export class VRConfigPanel {
private _scene: Scene;
private _handleMesh: Mesh;
private _advancedTexture: AdvancedDynamicTexture;
private _configObserver: Observer<AppConfigType>;
constructor(scene: Scene) {
// Initialize panel
}
public get handleMesh(): Mesh {
return this._handleMesh;
}
public show(): void {
this._handleMesh.setEnabled(true);
}
public hide(): void {
this._handleMesh.setEnabled(false);
}
public dispose(): void {
// Cleanup
}
}
```
- [ ] Create base mesh (plane) for panel backing
- [ ] Set up AdvancedDynamicTexture with appropriate resolution (1024x1024 or 2048x2048)
- [ ] Position panel at comfortable viewing distance (0.5-0.7m from camera)
- [ ] Make panel grabbable via Handle pattern
**Reference Files**:
- `src/menus/inputTextView.ts` - Handle pattern implementation
- `src/menus/configMenu.ts` - ADT usage example
### Phase 2: UI Layout Structure
- [ ] Create main container (StackPanel for vertical layout)
- [ ] Add title text at top ("Configuration")
- [ ] Create 5 section containers (one for each config group):
1. Location Snap
2. Rotation Snap
3. Fly Mode
4. Snap Turn
5. Label Rendering Mode
- [ ] Style containers with padding and spacing
- [ ] Add visual separators between sections
**ADT Components to Use**:
- `StackPanel` - Main vertical container
- `TextBlock` - Labels and section titles
- `Rectangle` - Containers and separators
**Reference**: `src/menus/configMenu.ts:44-89` for existing layout patterns
### Phase 3: Location Snap Section
- [ ] Add "Location Snap" label
- [ ] Create enable/disable toggle button
- Shows "Enabled" or "Disabled"
- Updates `appConfigInstance` on click
- [ ] Add RadioGroup for snap values:
- Options: 1cm (.01), 5cm (.05), 10cm (.1), 50cm (.5), 1m (1)
- Default: 10cm (.1)
- Disable when snap is off
- [ ] Wire up to `appConfigInstance.setGridSnap(value)`
- [ ] Subscribe to config changes to update UI
**ADT Components**:
- `Button` - Toggle switch
- `RadioButton` + `TextBlock` - Value selection
- Color coding: enabled (green/myColor), disabled (gray)
**Reference ConfigModal**: `src/react/pages/configModal.tsx:83-94`
### Phase 4: Rotation Snap Section
- [ ] Add "Rotation Snap" label
- [ ] Create enable/disable toggle button
- [ ] Add RadioGroup for rotation values:
- Options: 22.5°, 45°, 90°, 180°, 360°
- Default: 90°
- Disable when snap is off
- [ ] Wire up to `appConfigInstance.setRotateSnap(value)`
- [ ] Subscribe to config changes to update UI
**Reference ConfigModal**: `src/react/pages/configModal.tsx:96-108`
### Phase 5: Fly Mode Section
- [ ] Add "Fly Mode" label
- [ ] Create toggle button
- Shows "Fly Mode Enabled" or "Fly Mode Disabled"
- [ ] Wire up to `appConfigInstance.setFlyMode(value)`
- [ ] Subscribe to config changes to update UI
**Reference ConfigModal**: `src/react/pages/configModal.tsx:109-112`
### Phase 6: Snap Turn Section
- [ ] Add "Snap Turn" label
- [ ] Create enable/disable toggle button
- [ ] Add RadioGroup for snap turn angles:
- Options: 22.5°, 45°, 90°, 180°, 360°
- Default: 45°
- Disable when snap is off
- [ ] Wire up to `appConfigInstance.setTurnSnap(value)`
- [ ] Subscribe to config changes to update UI
**Reference ConfigModal**: `src/react/pages/configModal.tsx:113-125`
### Phase 7: Label Rendering Mode Section
- [ ] Add "Label Rendering Mode" label
- [ ] Create RadioGroup for rendering modes:
- Fixed
- Billboard (Always Face Camera)
- Dynamic (Coming Soon) - disabled
- Distance-based (Coming Soon) - disabled
- [ ] Wire up to `appConfigInstance.setLabelRenderingMode(value)`
- [ ] Subscribe to config changes to update UI
- [ ] Style disabled options with gray text
**Reference ConfigModal**: `src/react/pages/configModal.tsx:126-135`
### Phase 8: Integration with Toolbox
- [ ] Modify `src/diagram/diagramMenuManager.ts` to instantiate VRConfigPanel
- [ ] Add "Config" button to toolbox (similar to "Exit VR" button pattern)
- [ ] Wire up button click to show/hide panel
- [ ] Position panel relative to camera when shown (see `positionComponentsRelativeToCamera`)
- [ ] Add parent relationship to platform for movement tracking
**Reference**:
- `src/diagram/diagramMenuManager.ts:85-97` - Exit button creation
- `src/util/functions/groundMeshObserver.ts:127-222` - Component positioning
### Phase 9: Observable Integration
- [ ] Subscribe to `appConfigInstance.onConfigChangedObservable` in constructor
- [ ] Update all UI elements when config changes externally
- [ ] Ensure Observable cleanup in dispose() method
- [ ] Test config changes from both VR panel and 2D ConfigModal
**Pattern**:
```typescript
this._configObserver = appConfigInstance.onConfigChangedObservable.add((config) => {
// Update UI elements to reflect new config
this.updateLocationSnapUI(config.locationSnap);
this.updateRotationSnapUI(config.rotateSnap);
// ... etc
});
```
### Phase 10: Testing & Polish
- [ ] Test all toggle switches update config correctly
- [ ] Test all radio button selections update config correctly
- [ ] Verify config changes propagate to DiagramObjects (label mode, snap behavior)
- [ ] Test panel positioning in VR (comfortable viewing distance)
- [ ] Test panel grabbability via Handle
- [ ] Verify panel follows platform movement
- [ ] Test config persistence (localStorage)
- [ ] Test config synchronization between VR panel and 2D ConfigModal
- [ ] Add visual feedback for button clicks (color changes, animations)
- [ ] Ensure proper cleanup on panel disposal
- [ ] Test in both WebXR and desktop modes
## Code Patterns to Follow
### 1. Toggle Button Pattern
```typescript
const toggleButton = Button.CreateSimpleButton("toggle", "Enabled");
toggleButton.width = "200px";
toggleButton.height = "40px";
toggleButton.color = "white";
toggleButton.background = "green";
toggleButton.onPointerClickObservable.add(() => {
const newValue = !currentValue;
toggleButton.textBlock.text = newValue ? "Enabled" : "Disabled";
toggleButton.background = newValue ? "green" : "gray";
appConfigInstance.setSomeSetting(newValue);
});
```
### 2. RadioGroup Pattern
```typescript
const radioGroup = new SelectionPanel("snapValues");
const options = [
{ value: 0.01, label: "1cm" },
{ value: 0.1, label: "10cm" },
// ... more options
];
options.forEach(option => {
const radio = new RadioButton();
radio.width = "20px";
radio.height = "20px";
radio.isChecked = (option.value === currentValue);
radio.onIsCheckedChangedObservable.add((checked) => {
if (checked) {
appConfigInstance.setGridSnap(option.value);
}
});
// Add label next to radio button
});
```
### 3. Config Observer Pattern
```typescript
this._configObserver = appConfigInstance.onConfigChangedObservable.add((config) => {
this.updateUIFromConfig(config);
});
// In dispose():
if (this._configObserver) {
appConfigInstance.onConfigChangedObservable.remove(this._configObserver);
}
```
## Key Integration Points
### AppConfig Singleton
- Import: `import {appConfigInstance} from "../util/appConfig";`
- Read: `appConfigInstance.current.locationSnap`
- Write: `appConfigInstance.setGridSnap(0.1)`
- Subscribe: `appConfigInstance.onConfigChangedObservable.add(callback)`
### DiagramMenuManager
- Instantiate panel: `this._vrConfigPanel = new VRConfigPanel(this._scene);`
- Add button to toolbox: Follow exit button pattern in `setupExitButton()`
- Show panel: `this._vrConfigPanel.show();`
- Position panel: Follow pattern in `groundMeshObserver.ts:127-222`
### Handle Pattern
- Make panel grabbable by controllers
- Parent to platform for world movement
- Use `_handleMesh` as root for entire panel UI
## Reference Files
1. **src/menus/configMenu.ts** - Existing VR config implementation with ADT
2. **src/menus/inputTextView.ts** - Handle pattern and ADT setup
3. **src/react/pages/configModal.tsx** - UI structure and config sections
4. **src/util/appConfig.ts** - Config singleton and setter methods
5. **src/diagram/diagramMenuManager.ts** - Toolbox button creation
6. **src/util/functions/groundMeshObserver.ts** - Component positioning
## Success Criteria
- [ ] All 5 config sections implemented and functional
- [ ] Config changes in VR panel update appConfigInstance
- [ ] Config changes propagate to all DiagramObjects
- [ ] Panel is grabbable and repositionable
- [ ] Panel follows platform movement
- [ ] Config persists to localStorage
- [ ] Synchronized with 2D ConfigModal
- [ ] Comfortable viewing experience in VR
- [ ] No memory leaks (proper Observable cleanup)
## Notes
- Start hidden (only show when user clicks toolbox button)
- Position at ~0.5m in front of camera when opened
- Use Y-axis billboard mode to keep panel upright but allow rotation
- Consider adding "Close" button at bottom of panel
- Match color scheme with existing UI (myColor theme)
- Test with both left and right controller grabbing

138
docs/NAMING_CONVENTIONS.md Normal file
View File

@ -0,0 +1,138 @@
# Naming Conventions
## Tool and Material Naming
This document describes the naming conventions used for tools, materials, and related entities in the immersive WebXR application.
## Material Naming
Materials follow a consistent naming pattern based on their color:
**Format:** `material-{color}`
**Where:**
- `{color}` is the hex string representation of the material's color (e.g., `#ff0000` for red)
**Examples:**
- `material-#ff0000` - Red material
- `material-#00ff00` - Green material
- `material-#222222` - Dark gray material
**Implementation:**
```typescript
const material = new StandardMaterial("material-" + color.toHexString(), scene);
```
**Location:** Materials are created in:
- `src/toolbox/functions/buildColor.ts` - For toolbox color swatches
- `src/diagram/functions/buildMeshFromDiagramEntity.ts` - Fallback material creation via `buildMissingMaterial()`
## Tool Mesh Naming
Tool meshes use a compound naming pattern that includes both the tool type and color:
**Format:** `tool-{toolId}`
**Where:**
- `{toolId}` = `{toolType}-{color}`
- `{toolType}` is a value from the `ToolType` enum (e.g., `BOX`, `SPHERE`, `CYLINDER`, `CONE`, `PLANE`, `PERSON`)
- `{color}` is the hex string representation of the tool's color
**Examples:**
- `tool-BOX-#ff0000` - Red box tool
- `tool-SPHERE-#00ff00` - Green sphere tool
- `tool-CYLINDER-#0000ff` - Blue cylinder tool
- `tool-PLANE-#ffff00` - Yellow plane tool
**Implementation:**
```typescript
function toolId(tool: ToolType, color: Color3) {
return tool + "-" + color.toHexString();
}
const newItem = await buildMesh(tool, `tool-${id}`, colorParent.getScene());
// For example: `tool-BOX-#ff0000`
```
**Location:** Tool meshes are created in `src/toolbox/functions/buildTool.ts`
## Tool Colors
The application uses 16 predefined colors for the toolbox:
```typescript
const colors: string[] = [
"#222222", "#8b4513", "#006400", "#778899", // Row 1: Dark gray, Brown, Dark green, Light slate gray
"#4b0082", "#ff0000", "#ffa500", "#ffff00", // Row 2: Indigo, Red, Orange, Yellow
"#00ff00", "#00ffff", "#0000ff", "#ff00ff", // Row 3: Green, Cyan, Blue, Magenta
"#1e90ff", "#98fb98", "#ffe4b5", "#ff69b4" // Row 4: Dodger blue, Pale green, Moccasin, Hot pink
]
```
## Tool Types
Available tool types from the `ToolType` enum:
- `BOX` - Cube mesh
- `SPHERE` - Sphere mesh
- `CYLINDER` - Cylinder mesh
- `CONE` - Cone mesh
- `PLANE` - Flat plane mesh
- `PERSON` - Person/avatar mesh
## Material Color Access
When accessing material colors, use this priority order to handle both current and legacy materials:
```typescript
// For StandardMaterial
const stdMat = material as StandardMaterial;
const materialColor = stdMat.emissiveColor || stdMat.diffuseColor;
// Current rendering uses emissiveColor
// Legacy materials may have diffuseColor instead
```
## Rendering Modes
Materials can be rendered in three different modes, affecting how color properties are used:
### 1. Lightmap with Lighting
- Uses `diffuseColor` + `lightmapTexture`
- `disableLighting = false`
- Most expensive performance-wise
- Provides lighting illusion with actual lighting calculations
### 2. Unlit with Emissive Texture (Default)
- Uses `emissiveColor` + `emissiveTexture` (lightmap)
- `disableLighting = true`
- Best balance of visual quality and performance
- Provides lighting illusion without lighting calculations
### 3. Flat Emissive
- Uses only `emissiveColor`
- `disableLighting = true`
- Best performance
- No lighting illusion, flat shading
## Instance Naming
Instanced meshes (created from tool templates) follow this pattern:
**Format:** `tool-instance-{toolId}`
**Example:**
```typescript
const instance = new InstancedMesh("tool-instance-" + id, newItem);
// For example: `tool-instance-BOX-#ff0000`
```
These instances share materials with their source mesh and are used for visual feedback before creating actual diagram entities.
## Related Files
- `src/toolbox/functions/buildTool.ts` - Tool mesh creation and naming
- `src/toolbox/functions/buildColor.ts` - Material creation and color management
- `src/diagram/functions/buildMeshFromDiagramEntity.ts` - Diagram entity instantiation
- `src/toolbox/types/toolType.ts` - ToolType enum definition
- `src/util/lightmapGenerator.ts` - Lightmap texture generation and caching
- `src/util/renderingMode.ts` - Rendering mode enum and labels

View File

@ -1,96 +1,59 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Deep Diagram</title> <meta content="width=device-width, initial-scale=1" name="viewport"/>
<style> <meta content="An immersive vr diagramming experience based using webxr version 0.0.8-14 (2024-12-29Z)"
body { name="description">
<meta content="width=device-width, initial-scale=1, height=device-height" name="viewport">
<!--<link href="/styles.css" rel="stylesheet"> -->
<link href="/assets/dasfad/favicon-32x32.png" rel="icon" sizes="32x32" type="image/png">
<link href="/assets/dasfad/favicon-16x16.png" rel="icon" sizes="16x16" type="image/png">
<link href="/assets/dasfad/favicon-96x96.png" rel="icon" sizes="96x96" type="image/png">
<link as="fetch" href="/node_modules/.vite/deps/HavokPhysics.wasm" rel="preload">
<title>DASFAD</title>
<!-- <link as="script" href="/newRelic.js" rel="preload">
<script defer src="/newRelic.js"></script> -->
}
.loader { <link href="/manifest.webmanifest" rel="manifest"/>
position: fixed; <!--<script src='/niceware.js'></script>-->
left: 0px; <style>
top: 0px;
width: 200px;
height: 200px;
display: flex;
justify-content: center;
align-content: center;
flex-direction: column;
z-index: -1;
/*noinspection CssUnknownTarget*/
background: url("/spinner.gif");
background-position: center;
background-repeat: no-repeat;
text-align: center;
} </style>
#questLaunch {
position: fixed;
top: 30px;
right: 30px;
padding: 10px;
background: #000;
color: #fff;
font-size: 1.5em;
z-index: 100;
}
#download {
position: fixed;
z-index: 11;
width: 200px;
height: 20px;
position: absolute;
bottom: 50px;
left: 16px;
padding: 10px;
background: #000;
color: #fff;
}
</style>
<link as="script" href="/newRelic.js" rel="preload">
<script src="/newRelic.js"></script>
<meta content="width=device-width, initial-scale=1, height=device-height" name="viewport">
<meta content="An immersive vr diagramming experience based on a-frame and webxr" name="description">
<link href="/assets/favicon-16x16.png" rel=icon sizes="16x16" type="image/png">
<link href="/assets/favicon-32x32.png" rel=icon sizes="32x32" type="image/png">
<link href="/assets/favicon-96x96.png" rel=icon sizes="96x96" type="image/png">
<link href="/manifest.webmanifest" rel="manifest"/>
<script src='/niceware.js'></script>
</head> </head>
<body> <body>
<!--<div class="loader" id="loader">Loading...</div> -->
<div id="questLaunch"><a href="https://www.oculus.com/open_url/?url=https://www.deepdiagram.com/" target="_blank">Launch
On Quest</a>
</div>
<div id="download"><a href="#" id="downloadLink">Download Model</a></div>
<script> <script>
/*
var SpeechRecognition = SpeechRecognition || webkitSpeechRecognition
var SpeechGrammarList = SpeechGrammarList || window.webkitSpeechGrammarList
var SpeechRecognitionEvent = SpeechRecognitionEvent || webkitSpeechRecognitionEvent
var recognition = new SpeechRecognition();
recognition.continuous = false;
recognition.lang = 'en-US';
recognition.interimResults = true;
recognition.maxAlternatives = 1;
recognition.onresult = function(event) {
console.log(event.results[0][0].transcript);
}
recognition.onend = function() {
console.log("recognition ended");
recognition.start();
}
console.log("starting recognition");
recognition.start();
*/ /* if (typeof navigator.serviceWorker !== 'undefined') {
if (localStorage.getItem('serviceWorkerVersion') !== '11') {
caches.keys().then(cacheNames => {
cacheNames.forEach(cacheName => {
caches.delete(cacheName);
});
});
localStorage.setItem('serviceWorkerVersion', '11');
}
navigator.serviceWorker.register('/sw.js', {updateViaCache: 'none'});
}
*/
</script> </script>
<script type="module" src="./src/app.ts"></script> <div class="webApp" id="webApp">
</div>
<script defer src="/src/webApp.ts" type="module"></script>
<!--<video id="feed" controls="" autoplay="" name="media"><source src="https://listen.broadcastify.com/1drb2xhywkg8nvz.mp3?nc=49099&amp;xan=xtf9912b41c" type="audio/mpeg"></video> -->
<!--
<div class="scene">
<canvas id="gameCanvas"></canvas>
</div>
-->
<!--<script defer src="/src/vrApp.ts" type="module"></script>-->
</body> </body>
</html>
</html>

View File

@ -1,49 +0,0 @@
import {Handler, HandlerContext, HandlerEvent} from "@netlify/functions";
import axios from 'axios';
export const handler: Handler = async (event: HandlerEvent, context: HandlerContext) => {
try {
switch (event.httpMethod) {
case 'POST':
const apiKey = event.headers['api-key'];
const query = event.body;
const response = await axios.post('https://api.newrelic.com/graphql',
query,
{headers: {'Api-Key': apiKey, 'Content-Type': 'application/json'}});
const data = await response.data;
return {
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': 'https://cameras.immersiveidea.com',
'Access-Control-Allow-Credentials': 'true'
},
statusCode: 200,
body: JSON.stringify(data)
}
break;
case 'OPTIONS':
const headers = {
'Access-Control-Allow-Origin': 'https://cameras.immersiveidea.com',
'Access-Control-Allow-Credentials': 'true',
'Access-Control-Allow-Headers': 'content-type, api-key',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE'
};
return {
statusCode: 204,
headers
}
break;
default:
return {
statusCode: 405,
body: 'Method Not Allowed'
}
}
} catch (error) {
console.log(error);
return {
statusCode: 500,
body: JSON.stringify(error)
}
}
};

View File

@ -1,13 +0,0 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "Node",
"lib": [
"esnext"
],
"types": [
"@cloudflare/workers-types"
]
}
}

View File

@ -1,22 +0,0 @@
import {Handler, HandlerContext, HandlerEvent} from "@netlify/functions";
import axios from 'axios';
export const handler: Handler = async (event: HandlerEvent, context: HandlerContext) => {
try {
const response = await axios.post('https://api.assemblyai.com/v2/realtime/token', // use account token to get a temp user token
{expires_in: 3600}, // can set a TTL timer in seconds.
{headers: {authorization: process.env.VOICE_TOKEN}});
const data = await response.data;
return {
headers: {'Content-Type': 'application/json'},
statusCode: 200,
body: JSON.stringify(data)
}
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify(error)
}
}
};

48
newrelic.cjs Normal file
View File

@ -0,0 +1,48 @@
'use strict'
// Load .env.local first (has the secrets), then .env as fallback
require('dotenv').config({ path: '.env.local' });
require('dotenv').config();
/**
* New Relic Node.js APM Configuration
*
* This file configures the New Relic agent for backend monitoring.
* Requires NEW_RELIC_LICENSE_KEY environment variable to be set.
*
* Distributed tracing is enabled to correlate with browser agent traces.
*/
exports.config = {
app_name: ['dasfad-backend'],
license_key: process.env.NEW_RELIC_LICENSE_KEY,
distributed_tracing: {
enabled: true
},
logging: {
level: 'info'
},
application_logging: {
enabled: true,
forwarding: {
enabled: true,
max_samples_stored: 10000
},
local_decorating: {
enabled: true
}
},
allow_all_headers: true,
attributes: {
exclude: [
'request.headers.cookie',
'request.headers.authorization',
'request.headers.proxyAuthorization',
'request.headers.setCookie*',
'request.headers.x*',
'response.headers.cookie',
'response.headers.authorization',
'response.headers.proxyAuthorization',
'response.headers.setCookie*',
'response.headers.x*'
]
}
}

9811
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,48 +1,82 @@
{ {
"name": "immersive", "name": "immersive",
"private": true, "private": true,
"version": "0.0.1", "version": "0.0.8-48",
"type": "module", "type": "module",
"license": "MIT",
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=18.0.0"
}, },
"scripts": { "scripts": {
"dev": "vite", "dev": "node -r newrelic server.js",
"build": "vite build", "test": "vitest",
"preview": "vite preview", "build": "node versionBump.js && vite build",
"serve": "node server.js", "start": "NODE_ENV=production node -r newrelic server.js",
"start:api": "API_ONLY=true node -r newrelic server.js",
"socket": "node server/server.js",
"serverBuild": "cd server && tsc", "serverBuild": "cd server && tsc",
"havok": "cp ./node_modules/@babylonjs/havok/lib/esm/HavokPhysics.wasm ./node_modules/.vite/deps" "havok": "cp ./node_modules/@babylonjs/havok/lib/esm/HavokPhysics.wasm ./node_modules/.vite/deps"
}, },
"dependencies": { "dependencies": {
"@babylonjs/core": "^6.21.2", "@auth0/auth0-react": "^2.2.4",
"@babylonjs/gui": "^6.21.2", "@babylonjs/core": "^8.16.2",
"@babylonjs/havok": "1.1.4", "@babylonjs/gui": "^8.16.2",
"@babylonjs/inspector": "^6.21.2", "@babylonjs/havok": "1.3.4",
"@babylonjs/loaders": "^6.21.2", "@babylonjs/inspector": "^8.16.2",
"@babylonjs/serializers": "^6.21.2", "@babylonjs/loaders": "^8.16.2",
"@cloudflare/workers-types": "^4.20230821.0", "@babylonjs/materials": "^8.16.2",
"@netlify/functions": "^1.6.0", "@babylonjs/serializers": "^8.16.2",
"@typed-mxgraph/typed-mxgraph": "^1.0.8", "@emotion/react": "^11.13.0",
"@giphy/js-fetch-api": "^5.6.0",
"@giphy/react-components": "^9.6.0",
"@mantine/core": "^7.17.8",
"@mantine/form": "^7.17.8",
"@mantine/hooks": "^7.17.8",
"@maptiler/client": "1.8.1",
"@newrelic/browser-agent": "^1.306.0",
"@picovoice/cobra-web": "^2.0.3",
"@picovoice/eagle-web": "^1.0.0",
"@picovoice/web-voice-processor": "^4.0.9",
"@tabler/icons-react": "^3.14.0",
"@types/node": "^18.14.0", "@types/node": "^18.14.0",
"dexie": "^3.2.4", "@types/react": "^18.2.72",
"dexie-observable": "^4.0.1-beta.13", "@types/react-dom": "^18.2.22",
"earcut": "^2.2.4", "axios": "^1.10.0",
"canvas-hypertxt": "1.0.3",
"cors": "^2.8.5",
"dotenv": "^17.2.3",
"events": "^3.3.0",
"express": "^5.2.1",
"express-pouchdb": "^4.2.0",
"hash-wasm": "4.11.0",
"hls.js": "^1.1.4", "hls.js": "^1.1.4",
"loglevel": "^1.8.1", "js-crypto-aes": "1.0.6",
"mxgraph": "^4.2.2", "leveldown": "^6.1.1",
"niceware": "^4.0.0", "loglevel": "^1.9.1",
"p2p-data-channel": "^1.10.7", "meaningful-string": "^1.4.0",
"newrelic": "^13.9.1",
"peer-lite": "2.0.2",
"pouchdb": "^8.0.1",
"pouchdb-adapter-leveldb": "^9.0.0",
"pouchdb-adapter-memory": "^9.0.0",
"pouchdb-find": "^8.0.1",
"query-string": "^8.1.0", "query-string": "^8.1.0",
"recordrtc": "^5.6.2", "react-router-dom": "^6.26.1",
"ring-client-api": "11.7.7", "recordrtc": "^5.6.0",
"rfc4648": "^1.5.3",
"round": "^2.0.1", "round": "^2.0.1",
"uuid": "^9.0.0" "uint8-to-b64": "^1.0.2",
"use-pouchdb": "^2.0.2",
"uuid": "^9.0.1",
"vite-express": "^0.21.1",
"websocket": "^1.0.34",
"websocket-ts": "^2.1.5"
}, },
"devDependencies": { "devDependencies": {
"@types/dom-to-image": "^2.6.7",
"typescript": "^4.9.5", "typescript": "^4.9.5",
"vite": "^4.4.9", "vite": "^5.2.9",
"vite-plugin-api": "^0.1.11", "vite-plugin-cp": "^1.0.0",
"vite-plugin-cp": "^1.0.0" "vitest": "^1.4.0"
} }
} }

Binary file not shown.

1
public/_redirects Normal file
View File

@ -0,0 +1 @@
/db/* /index.html 200

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
public/assets/Android.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

@ -0,0 +1,40 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="3200"
height="1355.480324331485" viewBox="0 0 3200 1355.480324331485">
<g transform="scale(10) translate(10, 10)">
<defs id="SvgjsDefs1385">
<linearGradient id="SvgjsLinearGradient1390">
<stop id="SvgjsStop1391" stop-color="#905e26" offset="0"></stop>
<stop id="SvgjsStop1392" stop-color="#f5ec9b" offset="0.5"></stop>
<stop id="SvgjsStop1393" stop-color="#905e26" offset="1"></stop>
</linearGradient>
<linearGradient id="SvgjsLinearGradient1394">
<stop id="SvgjsStop1395" stop-color="#905e26" offset="0"></stop>
<stop id="SvgjsStop1396" stop-color="#f5ec9b" offset="0.5"></stop>
<stop id="SvgjsStop1397" stop-color="#905e26" offset="1"></stop>
</linearGradient>
</defs>
<g id="SvgjsG1386" featureKey="aMgJeN-0"
transform="matrix(1.5610770874511997,0,0,1.5610770874511997,71.94613967240352,-53.841545411371435)"
fill="url(#SvgjsLinearGradient1390)">
<path xmlns="http://www.w3.org/2000/svg"
d="M29.399,57.112c0.615,0.308,1.077,0.846,1.077,1.383c0,1.039-1.038,1.961-1.999,1.462l-15.223-7.881 c-0.846-0.499-1.576-0.808-1.576-1.884c0-1.115,0.692-1.385,1.576-1.922l15.223-7.881c1.038-0.346,1.999,0.424,1.999,1.462 c0,0.575-0.461,1.114-1.077,1.423l-13.761,6.918L29.399,57.112z"></path>
<path xmlns="http://www.w3.org/2000/svg"
d="M29.033,60.209c-0.208,0-0.413-0.052-0.608-0.152l-15.223-7.881c-0.086-0.05-0.165-0.095-0.242-0.141 c-0.748-0.431-1.395-0.804-1.395-1.843c0-1.057,0.594-1.406,1.346-1.849c0.093-0.054,0.187-0.11,0.284-0.169l15.229-7.885 c0.195-0.066,0.377-0.097,0.558-0.097c0.9,0,1.606,0.728,1.606,1.658c0,0.587-0.437,1.172-1.139,1.522l-13.562,6.818l13.562,6.818 c0.691,0.346,1.139,0.93,1.139,1.484C30.588,59.407,29.861,60.209,29.033,60.209z M28.982,40.419c-0.156,0-0.314,0.026-0.47,0.078 L13.306,48.37c-0.091,0.057-0.188,0.113-0.281,0.167c-0.743,0.439-1.234,0.728-1.234,1.655c0,0.91,0.537,1.219,1.281,1.648 c0.079,0.045,0.158,0.09,0.239,0.139l15.217,7.877c0.162,0.084,0.332,0.127,0.504,0.127c0.697,0,1.33-0.709,1.33-1.488 c0-0.465-0.407-0.98-1.014-1.283l-13.962-7.02l13.962-7.02c0.616-0.308,1.014-0.828,1.014-1.321 C30.363,41.048,29.756,40.419,28.982,40.419z"></path>
<path xmlns="http://www.w3.org/2000/svg"
d="M46.385,64.416c-0.231,0.691-0.922,1.077-1.614,0.961c-0.769-0.153-1.269-0.885-1.154-1.692 c0-0.076,0.039-0.191,0.077-0.307l9.88-27.831c0.23-0.692,0.922-1.038,1.614-0.923c0.73,0.154,1.269,0.885,1.153,1.652 c0,0.078-0.039,0.193-0.077,0.27L46.385,64.416z"></path>
<path xmlns="http://www.w3.org/2000/svg"
d="M45.016,65.511c-0.088,0-0.177-0.008-0.263-0.022c-0.837-0.167-1.371-0.949-1.247-1.819 c-0.001-0.076,0.038-0.193,0.079-0.318l9.883-27.842c0.207-0.618,0.778-1.02,1.457-1.02c0.094,0,0.188,0.009,0.281,0.023 c0.812,0.172,1.369,0.97,1.247,1.781c0.001,0.086-0.047,0.22-0.087,0.302l-9.875,27.856C46.281,65.084,45.688,65.511,45.016,65.511z M54.925,34.715c-0.58,0-1.068,0.34-1.244,0.867l-9.88,27.833c-0.035,0.104-0.07,0.213-0.07,0.27 c-0.108,0.767,0.349,1.439,1.062,1.582c0.071,0.012,0.147,0.018,0.223,0.018c0.575,0,1.083-0.363,1.263-0.904l9.881-27.872 c0.041-0.085,0.07-0.182,0.07-0.231c0.105-0.712-0.373-1.396-1.064-1.543C55.089,34.722,55.007,34.715,54.925,34.715z"></path>
<path xmlns="http://www.w3.org/2000/svg"
d="M84.362,50.192L70.6,43.274c-0.614-0.309-1.075-0.848-1.075-1.423c0-1.038,1.037-1.962,1.998-1.462l15.223,7.881 c0.885,0.537,1.576,0.807,1.576,1.922c0,1.076-0.73,1.385-1.576,1.884l-15.223,7.881c-0.961,0.499-1.998-0.423-1.998-1.462 c0-0.537,0.461-1.075,1.075-1.383L84.362,50.192z"></path>
<path xmlns="http://www.w3.org/2000/svg"
d="M70.967,60.209c-0.828,0-1.556-0.802-1.556-1.714c0-0.555,0.447-1.139,1.139-1.484l13.562-6.818L70.55,43.374 c-0.702-0.352-1.139-0.936-1.139-1.522c0-0.913,0.728-1.714,1.556-1.714c0.209,0,0.413,0.051,0.608,0.152l15.223,7.881 c0.104,0.062,0.198,0.119,0.29,0.173c0.753,0.442,1.347,0.792,1.347,1.849c0,1.039-0.646,1.412-1.395,1.843 c-0.078,0.046-0.157,0.091-0.237,0.138l-15.228,7.884C71.38,60.157,71.176,60.209,70.967,60.209z M70.967,40.363 c-0.696,0-1.33,0.709-1.33,1.488c0,0.493,0.397,1.013,1.014,1.321l13.962,7.02l-13.962,7.02c-0.606,0.305-1.014,0.82-1.014,1.283 c0,0.779,0.634,1.488,1.33,1.488c0.172,0,0.342-0.043,0.504-0.127l15.223-7.88c0.075-0.046,0.155-0.091,0.233-0.136 c0.744-0.43,1.282-0.738,1.282-1.648c0-0.928-0.491-1.216-1.235-1.655c-0.093-0.054-0.188-0.11-0.287-0.17l-15.216-7.876 C71.309,40.405,71.139,40.363,70.967,40.363z"></path>
</g>
<g id="SvgjsG1387" featureKey="8L6ael-0"
transform="matrix(3.168568052463937,0,0,3.168568052463937,-5.1330821487142195,52.17667742743372)"
fill="url(#SvgjsLinearGradient1394)">
<path d="M10.56 5.52 q1.22 0.46 2.2 1.48 q2.1 2.12 2.1 5.41 t-2.1 5.43 q-0.98 1.02 -2.2 1.48 q-1.26 0.52 -2.58 0.52 l-5.66 0 q-0.28 0 -0.49 -0.21 t-0.21 -0.49 l0 -1.44 q0 -0.3 0.21 -0.51 t0.49 -0.21 l5.6 0 q1.78 0 2.89 -1.3 t1.11 -3.26 t-1.11 -3.26 t-2.89 -1.3 l-5.6 0 q-0.28 0 -0.49 -0.21 t-0.21 -0.51 l0 -1.44 q0 -0.28 0.21 -0.49 t0.49 -0.21 l5.66 0 q1.32 0 2.58 0.52 z M32.208 18.86 q0.14 0.32 -0.07 0.65 t-0.57 0.33 l-13.32 0 q-0.18 0 -0.34 -0.09 t-0.24 -0.23 q-0.22 -0.32 -0.06 -0.66 l0.62 -1.46 q0.08 -0.2 0.26 -0.32 t0.38 -0.12 l9.32 0 l-3.28 -7.84 l-2.68 6.4 q-0.08 0.2 -0.25 0.31 t-0.39 0.11 l-1.68 0 q-0.38 0 -0.6 -0.32 q-0.08 -0.14 -0.1 -0.32 t0.04 -0.34 l4.06 -9.52 q0.08 -0.2 0.25 -0.32 t0.39 -0.12 l1.92 0 q0.22 0 0.39 0.12 t0.25 0.32 z M45.535999999999994 12.42 q0.86 0.54 1.31 1.32 t0.45 1.74 q0 2.26 -1.72 3.46 q-1.16 0.84 -3.14 1.06 l-0.08 0 q-0.28 0 -0.46 -0.18 q-0.24 -0.22 -0.24 -0.52 l0 -1.44 q0 -0.26 0.18 -0.46 t0.44 -0.24 q0.94 -0.1 1.46 -0.44 q0.4 -0.24 0.54 -0.62 q0.08 -0.22 0.08 -0.56 q0 -0.22 -0.08 -0.38 t-0.28 -0.3 q-0.58 -0.4 -1.5 -0.68 l-0.32 -0.1 q-1 -0.28 -1.84 -0.46 q-0.18 -0.04 -0.58 -0.14 l-0.22 -0.06 q-0.72 -0.18 -1.52 -0.5 q-1.18 -0.5 -1.94 -1.26 q-0.88 -0.88 -0.88 -2.32 q0 -1.98 1.52 -3.22 q1.04 -0.88 2.92 -1.12 q0.32 -0.04 0.55 0.18 t0.23 0.52 l0 1.44 q0 0.26 -0.16 0.46 t-0.41 0.23 t-0.51 0.11 q-0.58 0.22 -0.82 0.43 t-0.34 0.43 q-0.1 0.34 -0.1 0.64 q0 0.16 0.18 0.34 q0.28 0.28 0.82 0.5 q0.24 0.1 0.84 0.3 l2.5 0.62 l0.12 0.04 q0.94 0.26 1.36 0.4 q0.94 0.32 1.64 0.78 z M42.196 7.9 q-0.24 -0.06 -0.39 -0.25 t-0.15 -0.45 l0 -1.46 q0 -0.32 0.26 -0.54 q0.1 -0.1 0.26 -0.13 t0.3 -0.01 q1.76 0.28 2.86 1.2 q1.5 1.24 1.6 3.16 q0.02 0.28 -0.19 0.51 t-0.51 0.23 l-1.56 0 q-0.26 0 -0.46 -0.18 t-0.22 -0.44 q-0.08 -0.52 -0.34 -0.86 q-0.42 -0.5 -1.3 -0.76 q-0.06 0 -0.08 -0.02 l-0.08 0 z M39.855999999999995 17.08 q0.24 0.04 0.4 0.24 t0.16 0.44 l0 1.48 q0 0.32 -0.24 0.54 q-0.2 0.16 -0.46 0.16 l-0.1 0 q-1.86 -0.28 -3.06 -1.22 q-1.56 -1.24 -1.76 -3.46 q-0.04 -0.32 0.18 -0.55 t0.52 -0.23 l1.58 0 q0.28 0 0.48 0.19 t0.22 0.47 q0.06 1 0.98 1.54 q0.42 0.24 1.1 0.4 z M60.88399999999999 11.12 q0.3 0 0.51 0.21 t0.21 0.51 l0 1.48 q0 0.28 -0.21 0.49 t-0.51 0.21 l-6.58 0 l0 5.12 q0 0.28 -0.2 0.49 t-0.5 0.21 l-1.52 0 q-0.28 0 -0.49 -0.21 t-0.21 -0.49 l0 -7.3 q0 -0.3 0.21 -0.51 t0.49 -0.21 l8.8 0 z M61.78399999999999 5 q0.28 0 0.49 0.21 t0.21 0.49 l0 1.46 q0 0.3 -0.21 0.51 t-0.49 0.21 l-9.7 0 q-0.28 0 -0.49 -0.21 t-0.21 -0.51 l0 -1.46 q0 -0.28 0.21 -0.49 t0.49 -0.21 l9.7 0 z M79.512 18.86 q0.14 0.32 -0.07 0.65 t-0.57 0.33 l-13.32 0 q-0.18 0 -0.34 -0.09 t-0.24 -0.23 q-0.22 -0.32 -0.06 -0.66 l0.62 -1.46 q0.08 -0.2 0.26 -0.32 t0.38 -0.12 l9.32 0 l-3.28 -7.84 l-2.68 6.4 q-0.08 0.2 -0.25 0.31 t-0.39 0.11 l-1.68 0 q-0.38 0 -0.6 -0.32 q-0.08 -0.14 -0.1 -0.32 t0.04 -0.34 l4.06 -9.52 q0.08 -0.2 0.25 -0.32 t0.39 -0.12 l1.92 0 q0.22 0 0.39 0.12 t0.25 0.32 z M92 5.52 q1.22 0.46 2.2 1.48 q2.1 2.12 2.1 5.41 t-2.1 5.43 q-0.98 1.02 -2.2 1.48 q-1.26 0.52 -2.58 0.52 l-5.66 0 q-0.28 0 -0.49 -0.21 t-0.21 -0.49 l0 -1.44 q0 -0.3 0.21 -0.51 t0.49 -0.21 l5.6 0 q1.78 0 2.89 -1.3 t1.11 -3.26 t-1.11 -3.26 t-2.89 -1.3 l-5.6 0 q-0.28 0 -0.49 -0.21 t-0.21 -0.51 l0 -1.44 q0 -0.28 0.21 -0.49 t0.49 -0.21 l5.66 0 q1.32 0 2.58 0.52 z"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -0,0 +1,912 @@
%!PS-Adobe-3.0 EPSF-3.0
%Produced by poppler pdftops version: 22.05.0 (http://poppler.freedesktop.org)
%%Creator: Chromium
%%LanguageLevel: 3
%%DocumentSuppliedResources: (atend)
%%BoundingBox: 0 0 2400 1018
%%HiResBoundingBox: 0 0 2400 1017.12
%%DocumentSuppliedResources: (atend)
%%EndComments
%%BeginProlog
%%BeginResource: procset xpdf 3.00 0
%%Copyright: Copyright 1996-2011, 2022 Glyph & Cog, LLC
/xpdf 75 dict def xpdf begin
% PDF special state
/pdfDictSize 15 def
/pdfSetup {
/setpagedevice where {
pop 2 dict begin
/Policies 1 dict dup begin /PageSize 6 def end def
{ /Duplex true def } if
currentdict end setpagedevice
} {
pop
} ifelse
} def
/pdfSetupPaper {
% Change paper size, but only if different from previous paper size otherwise
% duplex fails. PLRM specifies a tolerance of 5 pts when matching paper size
% so we use the same when checking if the size changes.
/setpagedevice where {
pop currentpagedevice
/PageSize known {
2 copy
currentpagedevice /PageSize get aload pop
exch 4 1 roll
sub abs 5 gt
3 1 roll
sub abs 5 gt
or
} {
true
} ifelse
{
2 array astore
2 dict begin
/PageSize exch def
/ImagingBBox null def
currentdict end
setpagedevice
} {
pop pop
} ifelse
} {
pop
} ifelse
} def
/pdfStartPage {
pdfDictSize dict begin
/pdfFillCS [] def
/pdfFillXform {} def
/pdfStrokeCS [] def
/pdfStrokeXform {} def
/pdfFill [0] def
/pdfStroke [0] def
/pdfFillOP false def
/pdfStrokeOP false def
/pdfOPM false def
/pdfLastFill false def
/pdfLastStroke false def
/pdfTextMat [1 0 0 1 0 0] def
/pdfFontSize 0 def
/pdfCharSpacing 0 def
/pdfTextRender 0 def
/pdfPatternCS false def
/pdfTextRise 0 def
/pdfWordSpacing 0 def
/pdfHorizScaling 1 def
/pdfTextClipPath [] def
} def
/pdfEndPage { end } def
% PDF color state
/opm { dup /pdfOPM exch def
/setoverprintmode where{pop setoverprintmode}{pop}ifelse } def
/cs { /pdfFillXform exch def dup /pdfFillCS exch def
setcolorspace } def
/CS { /pdfStrokeXform exch def dup /pdfStrokeCS exch def
setcolorspace } def
/sc { pdfLastFill not { pdfFillCS setcolorspace } if
dup /pdfFill exch def aload pop pdfFillXform setcolor
/pdfLastFill true def /pdfLastStroke false def } def
/SC { pdfLastStroke not { pdfStrokeCS setcolorspace } if
dup /pdfStroke exch def aload pop pdfStrokeXform setcolor
/pdfLastStroke true def /pdfLastFill false def } def
/op { /pdfFillOP exch def
pdfLastFill { pdfFillOP setoverprint } if } def
/OP { /pdfStrokeOP exch def
pdfLastStroke { pdfStrokeOP setoverprint } if } def
/fCol {
pdfLastFill not {
pdfFillCS setcolorspace
pdfFill aload pop pdfFillXform setcolor
pdfFillOP setoverprint
/pdfLastFill true def /pdfLastStroke false def
} if
} def
/sCol {
pdfLastStroke not {
pdfStrokeCS setcolorspace
pdfStroke aload pop pdfStrokeXform setcolor
pdfStrokeOP setoverprint
/pdfLastStroke true def /pdfLastFill false def
} if
} def
% build a font
/pdfMakeFont {
4 3 roll findfont
4 2 roll matrix scale makefont
dup length dict begin
{ 1 index /FID ne { def } { pop pop } ifelse } forall
/Encoding exch def
currentdict
end
definefont pop
} def
/pdfMakeFont16 {
exch findfont
dup length dict begin
{ 1 index /FID ne { def } { pop pop } ifelse } forall
/WMode exch def
currentdict
end
definefont pop
} def
/pdfMakeFont16L3 {
1 index /CIDFont resourcestatus {
pop pop 1 index /CIDFont findresource /CIDFontType known
} {
false
} ifelse
{
0 eq { /Identity-H } { /Identity-V } ifelse
exch 1 array astore composefont pop
} {
pdfMakeFont16
} ifelse
} def
% graphics state operators
/q { gsave pdfDictSize dict begin } def
/Q {
end grestore
/pdfLastFill where {
pop
pdfLastFill {
pdfFillOP setoverprint
} {
pdfStrokeOP setoverprint
} ifelse
} if
/pdfOPM where {
pop
pdfOPM /setoverprintmode where{pop setoverprintmode}{pop}ifelse
} if
} def
/cm { concat } def
/d { setdash } def
/i { setflat } def
/j { setlinejoin } def
/J { setlinecap } def
/M { setmiterlimit } def
/w { setlinewidth } def
% path segment operators
/m { moveto } def
/l { lineto } def
/c { curveto } def
/re { 4 2 roll moveto 1 index 0 rlineto 0 exch rlineto
neg 0 rlineto closepath } def
/h { closepath } def
% path painting operators
/S { sCol stroke } def
/Sf { fCol stroke } def
/f { fCol fill } def
/f* { fCol eofill } def
% clipping operators
/W { clip newpath } def
/W* { eoclip newpath } def
/Ws { strokepath clip newpath } def
% text state operators
/Tc { /pdfCharSpacing exch def } def
/Tf { dup /pdfFontSize exch def
dup pdfHorizScaling mul exch matrix scale
pdfTextMat matrix concatmatrix dup 4 0 put dup 5 0 put
exch findfont exch makefont setfont } def
/Tr { /pdfTextRender exch def } def
/Tp { /pdfPatternCS exch def } def
/Ts { /pdfTextRise exch def } def
/Tw { /pdfWordSpacing exch def } def
/Tz { /pdfHorizScaling exch def } def
% text positioning operators
/Td { pdfTextMat transform moveto } def
/Tm { /pdfTextMat exch def } def
% text string operators
/xyshow where {
pop
/xyshow2 {
dup length array
0 2 2 index length 1 sub {
2 index 1 index 2 copy get 3 1 roll 1 add get
pdfTextMat dtransform
4 2 roll 2 copy 6 5 roll put 1 add 3 1 roll dup 4 2 roll put
} for
exch pop
xyshow
} def
}{
/xyshow2 {
currentfont /FontType get 0 eq {
0 2 3 index length 1 sub {
currentpoint 4 index 3 index 2 getinterval show moveto
2 copy get 2 index 3 2 roll 1 add get
pdfTextMat dtransform rmoveto
} for
} {
0 1 3 index length 1 sub {
currentpoint 4 index 3 index 1 getinterval show moveto
2 copy 2 mul get 2 index 3 2 roll 2 mul 1 add get
pdfTextMat dtransform rmoveto
} for
} ifelse
pop pop
} def
} ifelse
/cshow where {
pop
/xycp {
0 3 2 roll
{
pop pop currentpoint 3 2 roll
1 string dup 0 4 3 roll put false charpath moveto
2 copy get 2 index 2 index 1 add get
pdfTextMat dtransform rmoveto
2 add
} exch cshow
pop pop
} def
}{
/xycp {
currentfont /FontType get 0 eq {
0 2 3 index length 1 sub {
currentpoint 4 index 3 index 2 getinterval false charpath moveto
2 copy get 2 index 3 2 roll 1 add get
pdfTextMat dtransform rmoveto
} for
} {
0 1 3 index length 1 sub {
currentpoint 4 index 3 index 1 getinterval false charpath moveto
2 copy 2 mul get 2 index 3 2 roll 2 mul 1 add get
pdfTextMat dtransform rmoveto
} for
} ifelse
pop pop
} def
} ifelse
/Tj {
fCol
0 pdfTextRise pdfTextMat dtransform rmoveto
currentpoint 4 2 roll
pdfTextRender 1 and 0 eq {
2 copy xyshow2
} if
pdfTextRender 3 and dup 1 eq exch 2 eq or {
3 index 3 index moveto
2 copy
currentfont /FontType get 3 eq { fCol } { sCol } ifelse
xycp currentpoint stroke moveto
} if
pdfTextRender 4 and 0 ne {
4 2 roll moveto xycp
/pdfTextClipPath [ pdfTextClipPath aload pop
{/moveto cvx}
{/lineto cvx}
{/curveto cvx}
{/closepath cvx}
pathforall ] def
currentpoint newpath moveto
} {
pop pop pop pop
} ifelse
0 pdfTextRise neg pdfTextMat dtransform rmoveto
} def
/TJm { 0.001 mul pdfFontSize mul pdfHorizScaling mul neg 0
pdfTextMat dtransform rmoveto } def
/TJmV { 0.001 mul pdfFontSize mul neg 0 exch
pdfTextMat dtransform rmoveto } def
/Tclip { pdfTextClipPath cvx exec clip newpath
/pdfTextClipPath [] def } def
/Tclip* { pdfTextClipPath cvx exec eoclip newpath
/pdfTextClipPath [] def } def
% Level 2/3 image operators
/pdfImBuf 100 string def
/pdfImStr {
2 copy exch length lt {
2 copy get exch 1 add exch
} {
()
} ifelse
} def
/skipEOD {
{ currentfile pdfImBuf readline
not { pop exit } if
(%-EOD-) eq { exit } if } loop
} def
/pdfIm { image skipEOD } def
/pdfMask {
/ReusableStreamDecode filter
skipEOD
/maskStream exch def
} def
/pdfMaskEnd { maskStream closefile } def
/pdfMaskInit {
/maskArray exch def
/maskIdx 0 def
} def
/pdfMaskSrc {
maskIdx maskArray length lt {
maskArray maskIdx get
/maskIdx maskIdx 1 add def
} {
()
} ifelse
} def
/pdfImM { fCol imagemask skipEOD } def
/pr { 2 index 2 index 3 2 roll putinterval 4 add } def
/pdfImClip {
gsave
0 2 4 index length 1 sub {
dup 4 index exch 2 copy
get 5 index div put
1 add 3 index exch 2 copy
get 3 index div put
} for
pop pop rectclip
} def
/pdfImClipEnd { grestore } def
% shading operators
/colordelta {
false 0 1 3 index length 1 sub {
dup 4 index exch get 3 index 3 2 roll get sub abs 0.004 gt {
pop true
} if
} for
exch pop exch pop
} def
/funcCol { func n array astore } def
/funcSH {
dup 0 eq {
true
} {
dup 6 eq {
false
} {
4 index 4 index funcCol dup
6 index 4 index funcCol dup
3 1 roll colordelta 3 1 roll
5 index 5 index funcCol dup
3 1 roll colordelta 3 1 roll
6 index 8 index funcCol dup
3 1 roll colordelta 3 1 roll
colordelta or or or
} ifelse
} ifelse
{
1 add
4 index 3 index add 0.5 mul exch 4 index 3 index add 0.5 mul exch
6 index 6 index 4 index 4 index 4 index funcSH
2 index 6 index 6 index 4 index 4 index funcSH
6 index 2 index 4 index 6 index 4 index funcSH
5 3 roll 3 2 roll funcSH pop pop
} {
pop 3 index 2 index add 0.5 mul 3 index 2 index add 0.5 mul
funcCol sc
dup 4 index exch mat transform m
3 index 3 index mat transform l
1 index 3 index mat transform l
mat transform l pop pop h f*
} ifelse
} def
/axialCol {
dup 0 lt {
pop t0
} {
dup 1 gt {
pop t1
} {
dt mul t0 add
} ifelse
} ifelse
func n array astore
} def
/axialSH {
dup 0 eq {
true
} {
dup 8 eq {
false
} {
2 index axialCol 2 index axialCol colordelta
} ifelse
} ifelse
{
1 add 3 1 roll 2 copy add 0.5 mul
dup 4 3 roll exch 4 index axialSH
exch 3 2 roll axialSH
} {
pop 2 copy add 0.5 mul
axialCol sc
exch dup dx mul x0 add exch dy mul y0 add
3 2 roll dup dx mul x0 add exch dy mul y0 add
dx abs dy abs ge {
2 copy yMin sub dy mul dx div add yMin m
yMax sub dy mul dx div add yMax l
2 copy yMax sub dy mul dx div add yMax l
yMin sub dy mul dx div add yMin l
h f*
} {
exch 2 copy xMin sub dx mul dy div add xMin exch m
xMax sub dx mul dy div add xMax exch l
exch 2 copy xMax sub dx mul dy div add xMax exch l
xMin sub dx mul dy div add xMin exch l
h f*
} ifelse
} ifelse
} def
/radialCol {
dup t0 lt {
pop t0
} {
dup t1 gt {
pop t1
} if
} ifelse
func n array astore
} def
/radialSH {
dup 0 eq {
true
} {
dup 8 eq {
false
} {
2 index dt mul t0 add radialCol
2 index dt mul t0 add radialCol colordelta
} ifelse
} ifelse
{
1 add 3 1 roll 2 copy add 0.5 mul
dup 4 3 roll exch 4 index radialSH
exch 3 2 roll radialSH
} {
pop 2 copy add 0.5 mul dt mul t0 add
radialCol sc
encl {
exch dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
0 360 arc h
dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
360 0 arcn h f
} {
2 copy
dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
a1 a2 arcn
dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
a2 a1 arcn h
dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
a1 a2 arc
dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
a2 a1 arc h f
} ifelse
} ifelse
} def
end
%%EndResource
/CIDInit /ProcSet findresource begin
10 dict begin
begincmap
/CMapType 1 def
/CMapName /Identity-H def
/CIDSystemInfo 3 dict dup begin
/Registry (Adobe) def
/Ordering (Identity) def
/Supplement 0 def
end def
1 begincodespacerange
<0000> <ffff>
endcodespacerange
0 usefont
1 begincidrange
<0000> <ffff> 0
endcidrange
endcmap
currentdict CMapName exch /CMap defineresource pop
end
10 dict begin
begincmap
/CMapType 1 def
/CMapName /Identity-V def
/CIDSystemInfo 3 dict dup begin
/Registry (Adobe) def
/Ordering (Identity) def
/Supplement 0 def
end def
/WMode 1 def
1 begincodespacerange
<0000> <ffff>
endcodespacerange
0 usefont
1 begincidrange
<0000> <ffff> 0
endcidrange
endcmap
currentdict CMapName exch /CMap defineresource pop
end
end
%%EndProlog
%%BeginSetup
xpdf begin
%%EndSetup
pdfStartPage
%%EndPageSetup
[] 0 d
1 i
0 j
0 J
10 M
1 w
/DeviceGray {} cs
[0] sc
/DeviceGray {} CS
[0] SC
false op
false OP
{} settransfer
0 0 2400 1017.12 re
W
q
[0.24 0 0 -0.24 0 1017.12] cm
q
0 0 10000 4234.375 re
W*
q
[48.783241 0 0 48.766369 2560.8376 -1369.56274] cm
29.399 57.112 m
30.014 57.419998 30.476 57.958 30.476 58.494999 c
30.476 59.534 29.438 60.455997 28.476999 59.957001 c
13.254 52.076 l
12.408 51.577 11.678 51.268002 11.678 50.192001 c
11.678 49.077 12.37 48.807003 13.254 48.27 c
28.476999 40.389 l
29.514999 40.042999 30.476 40.813 30.476 41.851002 c
30.476 42.426003 30.014999 42.965 29.399 43.274002 c
15.638 50.192001 l
29.399 57.112 l
h
f
29.033001 60.209 m
28.825001 60.209 28.620001 60.157001 28.425001 60.056999 c
13.202002 52.175999 l
13.116001 52.125999 13.037002 52.080997 12.960002 52.035 c
12.212002 51.604 11.565002 51.230999 11.565002 50.192001 c
11.565002 49.135002 12.159002 48.786003 12.911002 48.343002 c
13.004003 48.289001 13.098002 48.233002 13.195003 48.174004 c
28.424004 40.289001 l
28.619003 40.223 28.801004 40.192001 28.982004 40.192001 c
29.882004 40.192001 30.588005 40.920002 30.588005 41.850002 c
30.588005 42.437004 30.151005 43.022003 29.449005 43.372002 c
15.887005 50.190002 l
29.449005 57.008003 l
30.140005 57.354004 30.588005 57.938004 30.588005 58.492004 c
30.587999 59.407001 29.861 60.209 29.033001 60.209 c
h
28.982 40.418999 m
28.826 40.418999 28.668001 40.445 28.512001 40.496998 c
13.306 48.369999 l
13.215 48.426998 13.118 48.482998 13.025 48.536999 c
12.282 48.975998 11.790999 49.264999 11.790999 50.191998 c
11.790999 51.101997 12.327999 51.410999 13.072 51.839996 c
13.151 51.884995 13.23 51.929996 13.311 51.978996 c
28.528 59.855995 l
28.690001 59.939995 28.860001 59.982994 29.032 59.982994 c
29.729 59.982994 30.362 59.273994 30.362 58.494995 c
30.362 58.029995 29.955 57.514996 29.348 57.211994 c
15.386 50.191994 l
29.348 43.171993 l
29.963999 42.863995 30.362 42.343994 30.362 41.850994 c
30.363001 41.048 29.756001 40.418999 28.982 40.418999 c
h
f
46.384998 64.416 m
46.153999 65.107002 45.462997 65.493004 44.771 65.376999 c
44.001999 65.223999 43.501999 64.491997 43.617001 63.684998 c
43.617001 63.608997 43.656002 63.493996 43.694 63.377998 c
53.574001 35.546997 l
53.804001 34.854996 54.496002 34.508999 55.188 34.623997 c
55.917999 34.777996 56.457001 35.508995 56.341 36.275997 c
56.341 36.353996 56.301998 36.468998 56.264 36.545998 c
46.384998 64.416 l
h
f
45.015999 65.511002 m
44.927998 65.511002 44.839001 65.502998 44.752998 65.488998 c
43.915997 65.321999 43.382 64.540001 43.505997 63.669998 c
43.504997 63.593998 43.543995 63.476997 43.584995 63.351997 c
53.467995 35.509998 l
53.674995 34.891998 54.245995 34.489998 54.924995 34.489998 c
55.018997 34.489998 55.112995 34.498997 55.205994 34.512997 c
56.017994 34.684998 56.574993 35.482998 56.452995 36.293995 c
56.453995 36.379993 56.405994 36.513996 56.365993 36.595993 c
46.490993 64.451996 l
46.280998 65.084 45.688 65.511002 45.015999 65.511002 c
h
54.924999 34.715 m
54.344997 34.715 53.856998 35.055 53.681 35.582001 c
43.800999 63.415001 l
43.765999 63.519001 43.730999 63.628002 43.730999 63.685001 c
43.622997 64.452003 44.079998 65.124001 44.792999 65.266998 c
44.863998 65.278999 44.939999 65.284996 45.015999 65.284996 c
45.591 65.284996 46.098999 64.921997 46.278999 64.380997 c
56.16 36.508995 l
56.201 36.423996 56.23 36.326996 56.23 36.277996 c
56.334999 35.565994 55.856998 34.881996 55.166 34.734997 c
55.089001 34.722 55.007 34.715 54.924999 34.715 c
h
f
84.362 50.192001 m
70.599998 43.273998 l
69.986 42.964996 69.525002 42.425999 69.525002 41.850998 c
69.525002 40.813 70.562004 39.888996 71.523003 40.388996 c
86.746002 48.269997 l
87.631004 48.806995 88.321999 49.076996 88.321999 50.191998 c
88.321999 51.267998 87.591995 51.576996 86.746002 52.075996 c
71.523003 59.956997 l
70.562004 60.455997 69.525002 59.533997 69.525002 58.494995 c
69.525002 57.957996 69.986 57.419994 70.599998 57.111996 c
84.362 50.192001 l
h
f
70.967003 60.209 m
70.139 60.209 69.411003 59.407001 69.411003 58.494999 c
69.411003 57.939999 69.858002 57.355999 70.550003 57.010998 c
84.112 50.192997 l
70.550003 43.374001 l
69.848 43.021999 69.411003 42.438 69.411003 41.852001 c
69.411003 40.939003 70.139 40.138 70.967003 40.138 c
71.176003 40.138 71.380005 40.188999 71.575005 40.290001 c
86.798004 48.171001 l
86.902 48.233002 86.996002 48.290001 87.088005 48.344002 c
87.841003 48.786003 88.435005 49.136002 88.435005 50.193001 c
88.435005 51.232002 87.789001 51.605 87.040009 52.035999 c
86.962006 52.082001 86.883011 52.126999 86.803009 52.174 c
71.575012 60.057999 l
71.379997 60.157001 71.176003 60.209 70.967003 60.209 c
h
70.967003 40.362999 m
70.271004 40.362999 69.637001 41.071999 69.637001 41.850998 c
69.637001 42.343998 70.034004 42.863998 70.651001 43.171997 c
84.612999 50.191998 l
70.651001 57.211998 l
70.044998 57.516998 69.637001 58.031998 69.637001 58.494999 c
69.637001 59.273998 70.271004 59.982998 70.967003 59.982998 c
71.139 59.982998 71.309006 59.939999 71.471001 59.855999 c
86.694 51.975998 l
86.768997 51.929996 86.848999 51.884998 86.927002 51.839996 c
87.671005 51.409996 88.209 51.101997 88.209 50.191998 c
88.209 49.263996 87.718002 48.975998 86.973999 48.536999 c
86.880997 48.482998 86.785995 48.426998 86.686996 48.367001 c
71.470993 40.491001 l
71.308998 40.404999 71.139 40.362999 70.967003 40.362999 c
h
f
Q
q
[99.016907 0 0 98.982658 152.13266 1942.3326] cm
10.56 5.52 m
11.373334 5.826667 12.106668 6.32 12.76 7 c
14.160001 8.413333 14.860001 10.216666 14.860001 12.41 c
14.860001 14.603334 14.160001 16.413334 12.76 17.84 c
12.106668 18.52 11.373334 19.013334 10.56 19.32 c
9.72 19.666666 8.860001 19.84 7.980001 19.84 c
2.320001 19.84 l
2.133334 19.84 1.970001 19.77 1.830001 19.630001 c
1.690001 19.490002 1.620001 19.326668 1.620001 19.140001 c
1.620001 17.700001 l
1.620001 17.500002 1.690001 17.330002 1.830001 17.190001 c
1.970001 17.049999 2.133334 16.98 2.320001 16.980001 c
7.920001 16.980001 l
9.106668 16.980001 10.070001 16.546669 10.81 15.680001 c
11.55 14.813335 11.92 13.726667 11.92 12.420001 c
11.92 11.113335 11.55 10.026668 10.81 9.160001 c
10.070001 8.293334 9.106668 7.860001 7.920001 7.860001 c
2.320001 7.860001 l
2.133334 7.860001 1.970001 7.79 1.830001 7.650001 c
1.690001 7.510001 1.620001 7.340001 1.620001 7.14 c
1.620001 5.7 l
1.620001 5.513333 1.690001 5.35 1.830001 5.21 c
1.970001 5.07 2.133334 5 2.320001 5 c
7.980001 5 l
8.860001 5 9.72 5.173333 10.56 5.52 c
h
32.208 18.860001 m
32.301334 19.073334 32.278 19.290001 32.138 19.51 c
31.998001 19.73 31.808001 19.84 31.568001 19.84 c
18.248001 19.84 l
18.128 19.84 18.014668 19.809999 17.908001 19.75 c
17.801334 19.690001 17.721334 19.613333 17.668001 19.52 c
17.521336 19.306667 17.501335 19.086668 17.608002 18.860001 c
18.228003 17.400002 l
18.281336 17.266668 18.368002 17.160002 18.488003 17.080002 c
18.608004 17.000002 18.734669 16.960003 18.868002 16.960001 c
28.188002 16.960001 l
24.908001 9.120001 l
22.228001 15.520001 l
22.174667 15.653334 22.091333 15.756667 21.978001 15.830001 c
21.864668 15.903335 21.734667 15.940002 21.588001 15.940001 c
19.908001 15.940001 l
19.654669 15.940001 19.454668 15.833334 19.308001 15.620001 c
19.254667 15.526668 19.221334 15.420001 19.208 15.300001 c
19.194666 15.180001 19.208 15.066669 19.248001 14.960001 c
23.308001 5.440001 l
23.361334 5.306667 23.444668 5.200001 23.558001 5.12 c
23.671333 5.04 23.801334 5 23.948 5.000001 c
25.868 5.000001 l
26.014666 5.000001 26.144667 5.04 26.257999 5.12 c
26.371332 5.2 26.454666 5.306667 26.507999 5.440001 c
32.208 18.860001 l
h
45.535999 12.42 m
46.109333 12.78 46.546001 13.22 46.846001 13.74 c
47.146 14.259999 47.296001 14.839999 47.296001 15.48 c
47.296001 16.986666 46.722668 18.139999 45.576 18.939999 c
44.802666 19.499998 43.756001 19.853333 42.436001 19.999998 c
42.355999 19.999998 l
42.169334 19.999998 42.015999 19.939999 41.896 19.819998 c
41.736 19.673332 41.655998 19.499998 41.655998 19.299997 c
41.655998 17.859997 l
41.655998 17.686663 41.716 17.533331 41.835999 17.399998 c
41.955997 17.266665 42.102665 17.186665 42.275997 17.159998 c
42.902664 17.09333 43.389328 16.946665 43.735996 16.719997 c
44.002663 16.559998 44.182663 16.353331 44.275997 16.099997 c
44.32933 15.95333 44.355999 15.766663 44.355999 15.539996 c
44.355999 15.39333 44.32933 15.266663 44.275997 15.159996 c
44.222664 15.05333 44.12933 14.953329 43.995998 14.859996 c
43.609329 14.593329 43.109329 14.366663 42.495998 14.179996 c
42.175999 14.079995 l
41.509331 13.893329 40.896 13.739995 40.335999 13.619995 c
40.216 13.593328 40.022663 13.546661 39.755997 13.479995 c
39.535995 13.419994 l
39.055996 13.299995 38.549328 13.133327 38.015995 12.919994 c
37.229328 12.586661 36.582661 12.166661 36.075996 11.659994 c
35.48933 11.073327 35.195995 10.299995 35.195995 9.339994 c
35.195995 8.019995 35.702663 6.946661 36.715996 6.119994 c
37.409328 5.533328 38.382664 5.159994 39.635994 4.999994 c
39.849327 4.973328 40.032661 5.033328 40.185993 5.179994 c
40.339325 5.326661 40.415993 5.499994 40.415993 5.699994 c
40.415993 7.139994 l
40.415993 7.313327 40.362659 7.466661 40.255993 7.599994 c
40.149326 7.733328 40.012661 7.809994 39.845993 7.829994 c
39.679325 7.849994 39.509327 7.886661 39.335995 7.939994 c
38.949326 8.08666 38.675995 8.229994 38.515995 8.369994 c
38.355995 8.509995 38.242664 8.653328 38.175995 8.799995 c
38.109329 9.026661 38.075996 9.239995 38.075996 9.439995 c
38.075996 9.546661 38.135998 9.659995 38.255997 9.779995 c
38.442661 9.966662 38.715996 10.133328 39.075996 10.279995 c
39.235996 10.346662 39.515999 10.446662 39.915997 10.579995 c
42.415997 11.199995 l
42.535995 11.239995 l
43.162663 11.413328 43.615993 11.546661 43.895996 11.639995 c
44.522663 11.853328 45.069328 12.113328 45.535995 12.419994 c
45.535999 12.42 l
h
42.195999 7.9 m
42.035999 7.86 41.905998 7.776667 41.806 7.65 c
41.706001 7.523334 41.656002 7.373334 41.655998 7.2 c
41.655998 5.74 l
41.655998 5.526667 41.742664 5.346667 41.915997 5.2 c
41.982662 5.133334 42.069328 5.09 42.175995 5.07 c
42.282661 5.05 42.38266 5.046667 42.475994 5.06 c
43.649326 5.246667 44.602661 5.646667 45.335995 6.26 c
46.335995 7.086667 46.869328 8.14 46.935993 9.42 c
46.949326 9.606667 46.885994 9.776667 46.745995 9.93 c
46.605995 10.083334 46.435997 10.160001 46.235996 10.16 c
44.675995 10.16 l
44.502663 10.16 44.349331 10.099999 44.215996 9.98 c
44.082661 9.86 44.009327 9.713333 43.995995 9.54 c
43.942661 9.193334 43.829327 8.906667 43.655994 8.68 c
43.375996 8.346667 42.942661 8.093333 42.355995 7.92 c
42.315994 7.92 42.289326 7.913333 42.275993 7.9 c
42.195992 7.9 l
42.195999 7.9 l
h
39.855999 17.08 m
40.015999 17.106667 40.149334 17.186666 40.256001 17.32 c
40.362667 17.453333 40.416 17.599998 40.416 17.76 c
40.416 19.24 l
40.416 19.453333 40.335999 19.633333 40.175999 19.780001 c
40.042664 19.886667 39.889332 19.940001 39.716 19.940001 c
39.616001 19.940001 l
38.375999 19.753334 37.355999 19.346666 36.556 18.720001 c
35.515999 17.893335 34.929333 16.740002 34.796001 15.260001 c
34.769333 15.046668 34.829334 14.863335 34.976002 14.710001 c
35.122669 14.556667 35.296001 14.480001 35.496002 14.480001 c
37.076004 14.480001 l
37.262669 14.480001 37.422668 14.543335 37.556004 14.670001 c
37.689339 14.796667 37.762672 14.953334 37.776005 15.140001 c
37.816006 15.806668 38.142673 16.320002 38.756004 16.68 c
39.036003 16.84 39.402668 16.973333 39.856003 17.08 c
39.855999 17.08 l
h
60.883999 11.12 m
61.084 11.12 61.253998 11.19 61.393997 11.33 c
61.533997 11.47 61.603996 11.64 61.603996 11.84 c
61.603996 13.32 l
61.603996 13.506666 61.533997 13.669999 61.393997 13.81 c
61.253998 13.95 61.084 14.02 60.883999 14.02 c
54.304001 14.02 l
54.304001 19.139999 l
54.304001 19.326666 54.237335 19.49 54.104 19.629999 c
53.970665 19.769999 53.804001 19.839998 53.604 19.839998 c
52.084 19.839998 l
51.897335 19.839998 51.734001 19.769999 51.593998 19.629999 c
51.453995 19.49 51.383995 19.326666 51.383999 19.139999 c
51.383999 11.839999 l
51.383999 11.639999 51.453999 11.469999 51.593998 11.329999 c
51.733997 11.189999 51.897331 11.119999 52.084 11.119999 c
60.883999 11.119999 l
60.883999 11.12 l
h
61.784 5 m
61.970665 5 62.133999 5.07 62.274002 5.21 c
62.414005 5.35 62.484005 5.513333 62.484001 5.7 c
62.484001 7.16 l
62.484001 7.36 62.414001 7.53 62.274002 7.67 c
62.134003 7.81 61.970669 7.88 61.784 7.88 c
52.084 7.88 l
51.897335 7.88 51.734001 7.81 51.593998 7.67 c
51.453995 7.53 51.383995 7.36 51.383999 7.16 c
51.383999 5.7 l
51.383999 5.513333 51.453999 5.35 51.593998 5.21 c
51.733997 5.07 51.897331 5.000001 52.084 5 c
61.784 5 l
h
79.512001 18.860001 m
79.605331 19.073334 79.582001 19.290001 79.442001 19.51 c
79.302002 19.73 79.112 19.84 78.872002 19.84 c
65.552002 19.84 l
65.431999 19.84 65.318672 19.809999 65.212006 19.75 c
65.105339 19.690001 65.025345 19.613333 64.972008 19.52 c
64.82534 19.306667 64.805344 19.086668 64.91201 18.860001 c
65.532013 17.400002 l
65.58535 17.266668 65.672012 17.160002 65.792015 17.080002 c
65.912018 17.000002 66.038681 16.960003 66.172012 16.960001 c
75.492012 16.960001 l
72.212013 9.120001 l
69.532013 15.520001 l
69.478676 15.653334 69.395348 15.756667 69.282013 15.830001 c
69.168678 15.903335 69.038681 15.940002 68.892014 15.940001 c
67.212013 15.940001 l
66.958679 15.940001 66.758682 15.833334 66.612015 15.620001 c
66.558678 15.526668 66.525345 15.420001 66.512016 15.300001 c
66.498688 15.180001 66.512016 15.066669 66.552017 14.960001 c
70.612015 5.440001 l
70.665352 5.306667 70.74868 5.200001 70.862015 5.12 c
70.975349 5.04 71.105347 5 71.252014 5.000001 c
73.172012 5.000001 l
73.31868 5.000001 73.448677 5.04 73.562012 5.12 c
73.675346 5.2 73.758675 5.306667 73.812012 5.440001 c
79.512001 18.860001 l
h
92 5.52 m
92.813332 5.826667 93.546669 6.32 94.199997 7 c
95.599998 8.413333 96.299995 10.216666 96.299995 12.41 c
96.299995 14.603334 95.599998 16.413334 94.199997 17.84 c
93.546661 18.52 92.813332 19.013334 92 19.32 c
91.159996 19.666666 90.299995 19.84 89.419998 19.84 c
83.759995 19.84 l
83.573326 19.84 83.409996 19.77 83.269997 19.630001 c
83.129997 19.490002 83.059998 19.326668 83.059998 19.140001 c
83.059998 17.700001 l
83.059998 17.500002 83.129997 17.330002 83.269997 17.190001 c
83.409996 17.049999 83.573326 16.98 83.759995 16.980001 c
89.359993 16.980001 l
90.546661 16.980001 91.509995 16.546669 92.249992 15.680001 c
92.98999 14.813335 93.359993 13.726667 93.359993 12.420001 c
93.359993 11.113335 92.98999 10.026668 92.249992 9.160001 c
91.509995 8.293334 90.546661 7.860001 89.359993 7.860001 c
83.759995 7.860001 l
83.573326 7.860001 83.409996 7.79 83.269997 7.650001 c
83.129997 7.510001 83.059998 7.340001 83.059998 7.14 c
83.059998 5.7 l
83.059998 5.513333 83.129997 5.35 83.269997 5.21 c
83.409996 5.07 83.573326 5 83.759995 5 c
89.419998 5 l
90.299995 5 91.159996 5.173333 92 5.52 c
h
f
Q
Q
Q
showpage
%%PageTrailer
pdfEndPage
%%Trailer
end
%%DocumentSuppliedResources:
%%EOF

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,920 @@
%!PS-Adobe-3.0 EPSF-3.0
%Produced by poppler pdftops version: 22.05.0 (http://poppler.freedesktop.org)
%%Creator: Chromium
%%LanguageLevel: 3
%%DocumentSuppliedResources: (atend)
%%BoundingBox: 0 0 2400 1018
%%HiResBoundingBox: 0 0 2400 1017.12
%%DocumentSuppliedResources: (atend)
%%EndComments
%%BeginProlog
%%BeginResource: procset xpdf 3.00 0
%%Copyright: Copyright 1996-2011, 2022 Glyph & Cog, LLC
/xpdf 75 dict def xpdf begin
% PDF special state
/pdfDictSize 15 def
/pdfSetup {
/setpagedevice where {
pop 2 dict begin
/Policies 1 dict dup begin /PageSize 6 def end def
{ /Duplex true def } if
currentdict end setpagedevice
} {
pop
} ifelse
} def
/pdfSetupPaper {
% Change paper size, but only if different from previous paper size otherwise
% duplex fails. PLRM specifies a tolerance of 5 pts when matching paper size
% so we use the same when checking if the size changes.
/setpagedevice where {
pop currentpagedevice
/PageSize known {
2 copy
currentpagedevice /PageSize get aload pop
exch 4 1 roll
sub abs 5 gt
3 1 roll
sub abs 5 gt
or
} {
true
} ifelse
{
2 array astore
2 dict begin
/PageSize exch def
/ImagingBBox null def
currentdict end
setpagedevice
} {
pop pop
} ifelse
} {
pop
} ifelse
} def
/pdfStartPage {
pdfDictSize dict begin
/pdfFillCS [] def
/pdfFillXform {} def
/pdfStrokeCS [] def
/pdfStrokeXform {} def
/pdfFill [0] def
/pdfStroke [0] def
/pdfFillOP false def
/pdfStrokeOP false def
/pdfOPM false def
/pdfLastFill false def
/pdfLastStroke false def
/pdfTextMat [1 0 0 1 0 0] def
/pdfFontSize 0 def
/pdfCharSpacing 0 def
/pdfTextRender 0 def
/pdfPatternCS false def
/pdfTextRise 0 def
/pdfWordSpacing 0 def
/pdfHorizScaling 1 def
/pdfTextClipPath [] def
} def
/pdfEndPage { end } def
% PDF color state
/opm { dup /pdfOPM exch def
/setoverprintmode where{pop setoverprintmode}{pop}ifelse } def
/cs { /pdfFillXform exch def dup /pdfFillCS exch def
setcolorspace } def
/CS { /pdfStrokeXform exch def dup /pdfStrokeCS exch def
setcolorspace } def
/sc { pdfLastFill not { pdfFillCS setcolorspace } if
dup /pdfFill exch def aload pop pdfFillXform setcolor
/pdfLastFill true def /pdfLastStroke false def } def
/SC { pdfLastStroke not { pdfStrokeCS setcolorspace } if
dup /pdfStroke exch def aload pop pdfStrokeXform setcolor
/pdfLastStroke true def /pdfLastFill false def } def
/op { /pdfFillOP exch def
pdfLastFill { pdfFillOP setoverprint } if } def
/OP { /pdfStrokeOP exch def
pdfLastStroke { pdfStrokeOP setoverprint } if } def
/fCol {
pdfLastFill not {
pdfFillCS setcolorspace
pdfFill aload pop pdfFillXform setcolor
pdfFillOP setoverprint
/pdfLastFill true def /pdfLastStroke false def
} if
} def
/sCol {
pdfLastStroke not {
pdfStrokeCS setcolorspace
pdfStroke aload pop pdfStrokeXform setcolor
pdfStrokeOP setoverprint
/pdfLastStroke true def /pdfLastFill false def
} if
} def
% build a font
/pdfMakeFont {
4 3 roll findfont
4 2 roll matrix scale makefont
dup length dict begin
{ 1 index /FID ne { def } { pop pop } ifelse } forall
/Encoding exch def
currentdict
end
definefont pop
} def
/pdfMakeFont16 {
exch findfont
dup length dict begin
{ 1 index /FID ne { def } { pop pop } ifelse } forall
/WMode exch def
currentdict
end
definefont pop
} def
/pdfMakeFont16L3 {
1 index /CIDFont resourcestatus {
pop pop 1 index /CIDFont findresource /CIDFontType known
} {
false
} ifelse
{
0 eq { /Identity-H } { /Identity-V } ifelse
exch 1 array astore composefont pop
} {
pdfMakeFont16
} ifelse
} def
% graphics state operators
/q { gsave pdfDictSize dict begin } def
/Q {
end grestore
/pdfLastFill where {
pop
pdfLastFill {
pdfFillOP setoverprint
} {
pdfStrokeOP setoverprint
} ifelse
} if
/pdfOPM where {
pop
pdfOPM /setoverprintmode where{pop setoverprintmode}{pop}ifelse
} if
} def
/cm { concat } def
/d { setdash } def
/i { setflat } def
/j { setlinejoin } def
/J { setlinecap } def
/M { setmiterlimit } def
/w { setlinewidth } def
% path segment operators
/m { moveto } def
/l { lineto } def
/c { curveto } def
/re { 4 2 roll moveto 1 index 0 rlineto 0 exch rlineto
neg 0 rlineto closepath } def
/h { closepath } def
% path painting operators
/S { sCol stroke } def
/Sf { fCol stroke } def
/f { fCol fill } def
/f* { fCol eofill } def
% clipping operators
/W { clip newpath } def
/W* { eoclip newpath } def
/Ws { strokepath clip newpath } def
% text state operators
/Tc { /pdfCharSpacing exch def } def
/Tf { dup /pdfFontSize exch def
dup pdfHorizScaling mul exch matrix scale
pdfTextMat matrix concatmatrix dup 4 0 put dup 5 0 put
exch findfont exch makefont setfont } def
/Tr { /pdfTextRender exch def } def
/Tp { /pdfPatternCS exch def } def
/Ts { /pdfTextRise exch def } def
/Tw { /pdfWordSpacing exch def } def
/Tz { /pdfHorizScaling exch def } def
% text positioning operators
/Td { pdfTextMat transform moveto } def
/Tm { /pdfTextMat exch def } def
% text string operators
/xyshow where {
pop
/xyshow2 {
dup length array
0 2 2 index length 1 sub {
2 index 1 index 2 copy get 3 1 roll 1 add get
pdfTextMat dtransform
4 2 roll 2 copy 6 5 roll put 1 add 3 1 roll dup 4 2 roll put
} for
exch pop
xyshow
} def
}{
/xyshow2 {
currentfont /FontType get 0 eq {
0 2 3 index length 1 sub {
currentpoint 4 index 3 index 2 getinterval show moveto
2 copy get 2 index 3 2 roll 1 add get
pdfTextMat dtransform rmoveto
} for
} {
0 1 3 index length 1 sub {
currentpoint 4 index 3 index 1 getinterval show moveto
2 copy 2 mul get 2 index 3 2 roll 2 mul 1 add get
pdfTextMat dtransform rmoveto
} for
} ifelse
pop pop
} def
} ifelse
/cshow where {
pop
/xycp {
0 3 2 roll
{
pop pop currentpoint 3 2 roll
1 string dup 0 4 3 roll put false charpath moveto
2 copy get 2 index 2 index 1 add get
pdfTextMat dtransform rmoveto
2 add
} exch cshow
pop pop
} def
}{
/xycp {
currentfont /FontType get 0 eq {
0 2 3 index length 1 sub {
currentpoint 4 index 3 index 2 getinterval false charpath moveto
2 copy get 2 index 3 2 roll 1 add get
pdfTextMat dtransform rmoveto
} for
} {
0 1 3 index length 1 sub {
currentpoint 4 index 3 index 1 getinterval false charpath moveto
2 copy 2 mul get 2 index 3 2 roll 2 mul 1 add get
pdfTextMat dtransform rmoveto
} for
} ifelse
pop pop
} def
} ifelse
/Tj {
fCol
0 pdfTextRise pdfTextMat dtransform rmoveto
currentpoint 4 2 roll
pdfTextRender 1 and 0 eq {
2 copy xyshow2
} if
pdfTextRender 3 and dup 1 eq exch 2 eq or {
3 index 3 index moveto
2 copy
currentfont /FontType get 3 eq { fCol } { sCol } ifelse
xycp currentpoint stroke moveto
} if
pdfTextRender 4 and 0 ne {
4 2 roll moveto xycp
/pdfTextClipPath [ pdfTextClipPath aload pop
{/moveto cvx}
{/lineto cvx}
{/curveto cvx}
{/closepath cvx}
pathforall ] def
currentpoint newpath moveto
} {
pop pop pop pop
} ifelse
0 pdfTextRise neg pdfTextMat dtransform rmoveto
} def
/TJm { 0.001 mul pdfFontSize mul pdfHorizScaling mul neg 0
pdfTextMat dtransform rmoveto } def
/TJmV { 0.001 mul pdfFontSize mul neg 0 exch
pdfTextMat dtransform rmoveto } def
/Tclip { pdfTextClipPath cvx exec clip newpath
/pdfTextClipPath [] def } def
/Tclip* { pdfTextClipPath cvx exec eoclip newpath
/pdfTextClipPath [] def } def
% Level 2/3 image operators
/pdfImBuf 100 string def
/pdfImStr {
2 copy exch length lt {
2 copy get exch 1 add exch
} {
()
} ifelse
} def
/skipEOD {
{ currentfile pdfImBuf readline
not { pop exit } if
(%-EOD-) eq { exit } if } loop
} def
/pdfIm { image skipEOD } def
/pdfMask {
/ReusableStreamDecode filter
skipEOD
/maskStream exch def
} def
/pdfMaskEnd { maskStream closefile } def
/pdfMaskInit {
/maskArray exch def
/maskIdx 0 def
} def
/pdfMaskSrc {
maskIdx maskArray length lt {
maskArray maskIdx get
/maskIdx maskIdx 1 add def
} {
()
} ifelse
} def
/pdfImM { fCol imagemask skipEOD } def
/pr { 2 index 2 index 3 2 roll putinterval 4 add } def
/pdfImClip {
gsave
0 2 4 index length 1 sub {
dup 4 index exch 2 copy
get 5 index div put
1 add 3 index exch 2 copy
get 3 index div put
} for
pop pop rectclip
} def
/pdfImClipEnd { grestore } def
% shading operators
/colordelta {
false 0 1 3 index length 1 sub {
dup 4 index exch get 3 index 3 2 roll get sub abs 0.004 gt {
pop true
} if
} for
exch pop exch pop
} def
/funcCol { func n array astore } def
/funcSH {
dup 0 eq {
true
} {
dup 6 eq {
false
} {
4 index 4 index funcCol dup
6 index 4 index funcCol dup
3 1 roll colordelta 3 1 roll
5 index 5 index funcCol dup
3 1 roll colordelta 3 1 roll
6 index 8 index funcCol dup
3 1 roll colordelta 3 1 roll
colordelta or or or
} ifelse
} ifelse
{
1 add
4 index 3 index add 0.5 mul exch 4 index 3 index add 0.5 mul exch
6 index 6 index 4 index 4 index 4 index funcSH
2 index 6 index 6 index 4 index 4 index funcSH
6 index 2 index 4 index 6 index 4 index funcSH
5 3 roll 3 2 roll funcSH pop pop
} {
pop 3 index 2 index add 0.5 mul 3 index 2 index add 0.5 mul
funcCol sc
dup 4 index exch mat transform m
3 index 3 index mat transform l
1 index 3 index mat transform l
mat transform l pop pop h f*
} ifelse
} def
/axialCol {
dup 0 lt {
pop t0
} {
dup 1 gt {
pop t1
} {
dt mul t0 add
} ifelse
} ifelse
func n array astore
} def
/axialSH {
dup 0 eq {
true
} {
dup 8 eq {
false
} {
2 index axialCol 2 index axialCol colordelta
} ifelse
} ifelse
{
1 add 3 1 roll 2 copy add 0.5 mul
dup 4 3 roll exch 4 index axialSH
exch 3 2 roll axialSH
} {
pop 2 copy add 0.5 mul
axialCol sc
exch dup dx mul x0 add exch dy mul y0 add
3 2 roll dup dx mul x0 add exch dy mul y0 add
dx abs dy abs ge {
2 copy yMin sub dy mul dx div add yMin m
yMax sub dy mul dx div add yMax l
2 copy yMax sub dy mul dx div add yMax l
yMin sub dy mul dx div add yMin l
h f*
} {
exch 2 copy xMin sub dx mul dy div add xMin exch m
xMax sub dx mul dy div add xMax exch l
exch 2 copy xMax sub dx mul dy div add xMax exch l
xMin sub dx mul dy div add xMin exch l
h f*
} ifelse
} ifelse
} def
/radialCol {
dup t0 lt {
pop t0
} {
dup t1 gt {
pop t1
} if
} ifelse
func n array astore
} def
/radialSH {
dup 0 eq {
true
} {
dup 8 eq {
false
} {
2 index dt mul t0 add radialCol
2 index dt mul t0 add radialCol colordelta
} ifelse
} ifelse
{
1 add 3 1 roll 2 copy add 0.5 mul
dup 4 3 roll exch 4 index radialSH
exch 3 2 roll radialSH
} {
pop 2 copy add 0.5 mul dt mul t0 add
radialCol sc
encl {
exch dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
0 360 arc h
dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
360 0 arcn h f
} {
2 copy
dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
a1 a2 arcn
dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
a2 a1 arcn h
dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
a1 a2 arc
dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
a2 a1 arc h f
} ifelse
} ifelse
} def
end
%%EndResource
/CIDInit /ProcSet findresource begin
10 dict begin
begincmap
/CMapType 1 def
/CMapName /Identity-H def
/CIDSystemInfo 3 dict dup begin
/Registry (Adobe) def
/Ordering (Identity) def
/Supplement 0 def
end def
1 begincodespacerange
<0000> <ffff>
endcodespacerange
0 usefont
1 begincidrange
<0000> <ffff> 0
endcidrange
endcmap
currentdict CMapName exch /CMap defineresource pop
end
10 dict begin
begincmap
/CMapType 1 def
/CMapName /Identity-V def
/CIDSystemInfo 3 dict dup begin
/Registry (Adobe) def
/Ordering (Identity) def
/Supplement 0 def
end def
/WMode 1 def
1 begincodespacerange
<0000> <ffff>
endcodespacerange
0 usefont
1 begincidrange
<0000> <ffff> 0
endcidrange
endcmap
currentdict CMapName exch /CMap defineresource pop
end
end
%%EndProlog
%%BeginSetup
xpdf begin
%%EndSetup
pdfStartPage
%%EndPageSetup
[] 0 d
1 i
0 j
0 J
10 M
1 w
/DeviceGray {} cs
[0] sc
/DeviceGray {} CS
[0] SC
false op
false OP
{} settransfer
0 0 2400 1017.12 re
W
q
[0.24 0 0 -0.24 0 1017.12] cm
q
0 0 10000 4234.375 re
W*
q
[48.783241 0 0 48.766369 2560.8376 -1369.56274] cm
/DeviceRGB {} CS
[1 1 1] SC
/DeviceRGB {} cs
[1 1 1] sc
29.399 57.112 m
30.014 57.419998 30.476 57.958 30.476 58.494999 c
30.476 59.534 29.438 60.455997 28.476999 59.957001 c
13.254 52.076 l
12.408 51.577 11.678 51.268002 11.678 50.192001 c
11.678 49.077 12.37 48.807003 13.254 48.27 c
28.476999 40.389 l
29.514999 40.042999 30.476 40.813 30.476 41.851002 c
30.476 42.426003 30.014999 42.965 29.399 43.274002 c
15.638 50.192001 l
29.399 57.112 l
h
f
29.033001 60.209 m
28.825001 60.209 28.620001 60.157001 28.425001 60.056999 c
13.202002 52.175999 l
13.116001 52.125999 13.037002 52.080997 12.960002 52.035 c
12.212002 51.604 11.565002 51.230999 11.565002 50.192001 c
11.565002 49.135002 12.159002 48.786003 12.911002 48.343002 c
13.004003 48.289001 13.098002 48.233002 13.195003 48.174004 c
28.424004 40.289001 l
28.619003 40.223 28.801004 40.192001 28.982004 40.192001 c
29.882004 40.192001 30.588005 40.920002 30.588005 41.850002 c
30.588005 42.437004 30.151005 43.022003 29.449005 43.372002 c
15.887005 50.190002 l
29.449005 57.008003 l
30.140005 57.354004 30.588005 57.938004 30.588005 58.492004 c
30.587999 59.407001 29.861 60.209 29.033001 60.209 c
h
28.982 40.418999 m
28.826 40.418999 28.668001 40.445 28.512001 40.496998 c
13.306 48.369999 l
13.215 48.426998 13.118 48.482998 13.025 48.536999 c
12.282 48.975998 11.790999 49.264999 11.790999 50.191998 c
11.790999 51.101997 12.327999 51.410999 13.072 51.839996 c
13.151 51.884995 13.23 51.929996 13.311 51.978996 c
28.528 59.855995 l
28.690001 59.939995 28.860001 59.982994 29.032 59.982994 c
29.729 59.982994 30.362 59.273994 30.362 58.494995 c
30.362 58.029995 29.955 57.514996 29.348 57.211994 c
15.386 50.191994 l
29.348 43.171993 l
29.963999 42.863995 30.362 42.343994 30.362 41.850994 c
30.363001 41.048 29.756001 40.418999 28.982 40.418999 c
h
f
46.384998 64.416 m
46.153999 65.107002 45.462997 65.493004 44.771 65.376999 c
44.001999 65.223999 43.501999 64.491997 43.617001 63.684998 c
43.617001 63.608997 43.656002 63.493996 43.694 63.377998 c
53.574001 35.546997 l
53.804001 34.854996 54.496002 34.508999 55.188 34.623997 c
55.917999 34.777996 56.457001 35.508995 56.341 36.275997 c
56.341 36.353996 56.301998 36.468998 56.264 36.545998 c
46.384998 64.416 l
h
f
45.015999 65.511002 m
44.927998 65.511002 44.839001 65.502998 44.752998 65.488998 c
43.915997 65.321999 43.382 64.540001 43.505997 63.669998 c
43.504997 63.593998 43.543995 63.476997 43.584995 63.351997 c
53.467995 35.509998 l
53.674995 34.891998 54.245995 34.489998 54.924995 34.489998 c
55.018997 34.489998 55.112995 34.498997 55.205994 34.512997 c
56.017994 34.684998 56.574993 35.482998 56.452995 36.293995 c
56.453995 36.379993 56.405994 36.513996 56.365993 36.595993 c
46.490993 64.451996 l
46.280998 65.084 45.688 65.511002 45.015999 65.511002 c
h
54.924999 34.715 m
54.344997 34.715 53.856998 35.055 53.681 35.582001 c
43.800999 63.415001 l
43.765999 63.519001 43.730999 63.628002 43.730999 63.685001 c
43.622997 64.452003 44.079998 65.124001 44.792999 65.266998 c
44.863998 65.278999 44.939999 65.284996 45.015999 65.284996 c
45.591 65.284996 46.098999 64.921997 46.278999 64.380997 c
56.16 36.508995 l
56.201 36.423996 56.23 36.326996 56.23 36.277996 c
56.334999 35.565994 55.856998 34.881996 55.166 34.734997 c
55.089001 34.722 55.007 34.715 54.924999 34.715 c
h
f
84.362 50.192001 m
70.599998 43.273998 l
69.986 42.964996 69.525002 42.425999 69.525002 41.850998 c
69.525002 40.813 70.562004 39.888996 71.523003 40.388996 c
86.746002 48.269997 l
87.631004 48.806995 88.321999 49.076996 88.321999 50.191998 c
88.321999 51.267998 87.591995 51.576996 86.746002 52.075996 c
71.523003 59.956997 l
70.562004 60.455997 69.525002 59.533997 69.525002 58.494995 c
69.525002 57.957996 69.986 57.419994 70.599998 57.111996 c
84.362 50.192001 l
h
f
70.967003 60.209 m
70.139 60.209 69.411003 59.407001 69.411003 58.494999 c
69.411003 57.939999 69.858002 57.355999 70.550003 57.010998 c
84.112 50.192997 l
70.550003 43.374001 l
69.848 43.021999 69.411003 42.438 69.411003 41.852001 c
69.411003 40.939003 70.139 40.138 70.967003 40.138 c
71.176003 40.138 71.380005 40.188999 71.575005 40.290001 c
86.798004 48.171001 l
86.902 48.233002 86.996002 48.290001 87.088005 48.344002 c
87.841003 48.786003 88.435005 49.136002 88.435005 50.193001 c
88.435005 51.232002 87.789001 51.605 87.040009 52.035999 c
86.962006 52.082001 86.883011 52.126999 86.803009 52.174 c
71.575012 60.057999 l
71.379997 60.157001 71.176003 60.209 70.967003 60.209 c
h
70.967003 40.362999 m
70.271004 40.362999 69.637001 41.071999 69.637001 41.850998 c
69.637001 42.343998 70.034004 42.863998 70.651001 43.171997 c
84.612999 50.191998 l
70.651001 57.211998 l
70.044998 57.516998 69.637001 58.031998 69.637001 58.494999 c
69.637001 59.273998 70.271004 59.982998 70.967003 59.982998 c
71.139 59.982998 71.309006 59.939999 71.471001 59.855999 c
86.694 51.975998 l
86.768997 51.929996 86.848999 51.884998 86.927002 51.839996 c
87.671005 51.409996 88.209 51.101997 88.209 50.191998 c
88.209 49.263996 87.718002 48.975998 86.973999 48.536999 c
86.880997 48.482998 86.785995 48.426998 86.686996 48.367001 c
71.470993 40.491001 l
71.308998 40.404999 71.139 40.362999 70.967003 40.362999 c
h
f
Q
q
[99.016907 0 0 98.982658 152.13266 1942.3326] cm
/DeviceRGB {} CS
[1 1 1] SC
/DeviceRGB {} cs
[1 1 1] sc
10.56 5.52 m
11.373334 5.826667 12.106668 6.32 12.76 7 c
14.160001 8.413333 14.860001 10.216666 14.860001 12.41 c
14.860001 14.603334 14.160001 16.413334 12.76 17.84 c
12.106668 18.52 11.373334 19.013334 10.56 19.32 c
9.72 19.666666 8.860001 19.84 7.980001 19.84 c
2.320001 19.84 l
2.133334 19.84 1.970001 19.77 1.830001 19.630001 c
1.690001 19.490002 1.620001 19.326668 1.620001 19.140001 c
1.620001 17.700001 l
1.620001 17.500002 1.690001 17.330002 1.830001 17.190001 c
1.970001 17.049999 2.133334 16.98 2.320001 16.980001 c
7.920001 16.980001 l
9.106668 16.980001 10.070001 16.546669 10.81 15.680001 c
11.55 14.813335 11.92 13.726667 11.92 12.420001 c
11.92 11.113335 11.55 10.026668 10.81 9.160001 c
10.070001 8.293334 9.106668 7.860001 7.920001 7.860001 c
2.320001 7.860001 l
2.133334 7.860001 1.970001 7.79 1.830001 7.650001 c
1.690001 7.510001 1.620001 7.340001 1.620001 7.14 c
1.620001 5.7 l
1.620001 5.513333 1.690001 5.35 1.830001 5.21 c
1.970001 5.07 2.133334 5 2.320001 5 c
7.980001 5 l
8.860001 5 9.72 5.173333 10.56 5.52 c
h
32.208 18.860001 m
32.301334 19.073334 32.278 19.290001 32.138 19.51 c
31.998001 19.73 31.808001 19.84 31.568001 19.84 c
18.248001 19.84 l
18.128 19.84 18.014668 19.809999 17.908001 19.75 c
17.801334 19.690001 17.721334 19.613333 17.668001 19.52 c
17.521336 19.306667 17.501335 19.086668 17.608002 18.860001 c
18.228003 17.400002 l
18.281336 17.266668 18.368002 17.160002 18.488003 17.080002 c
18.608004 17.000002 18.734669 16.960003 18.868002 16.960001 c
28.188002 16.960001 l
24.908001 9.120001 l
22.228001 15.520001 l
22.174667 15.653334 22.091333 15.756667 21.978001 15.830001 c
21.864668 15.903335 21.734667 15.940002 21.588001 15.940001 c
19.908001 15.940001 l
19.654669 15.940001 19.454668 15.833334 19.308001 15.620001 c
19.254667 15.526668 19.221334 15.420001 19.208 15.300001 c
19.194666 15.180001 19.208 15.066669 19.248001 14.960001 c
23.308001 5.440001 l
23.361334 5.306667 23.444668 5.200001 23.558001 5.12 c
23.671333 5.04 23.801334 5 23.948 5.000001 c
25.868 5.000001 l
26.014666 5.000001 26.144667 5.04 26.257999 5.12 c
26.371332 5.2 26.454666 5.306667 26.507999 5.440001 c
32.208 18.860001 l
h
45.535999 12.42 m
46.109333 12.78 46.546001 13.22 46.846001 13.74 c
47.146 14.259999 47.296001 14.839999 47.296001 15.48 c
47.296001 16.986666 46.722668 18.139999 45.576 18.939999 c
44.802666 19.499998 43.756001 19.853333 42.436001 19.999998 c
42.355999 19.999998 l
42.169334 19.999998 42.015999 19.939999 41.896 19.819998 c
41.736 19.673332 41.655998 19.499998 41.655998 19.299997 c
41.655998 17.859997 l
41.655998 17.686663 41.716 17.533331 41.835999 17.399998 c
41.955997 17.266665 42.102665 17.186665 42.275997 17.159998 c
42.902664 17.09333 43.389328 16.946665 43.735996 16.719997 c
44.002663 16.559998 44.182663 16.353331 44.275997 16.099997 c
44.32933 15.95333 44.355999 15.766663 44.355999 15.539996 c
44.355999 15.39333 44.32933 15.266663 44.275997 15.159996 c
44.222664 15.05333 44.12933 14.953329 43.995998 14.859996 c
43.609329 14.593329 43.109329 14.366663 42.495998 14.179996 c
42.175999 14.079995 l
41.509331 13.893329 40.896 13.739995 40.335999 13.619995 c
40.216 13.593328 40.022663 13.546661 39.755997 13.479995 c
39.535995 13.419994 l
39.055996 13.299995 38.549328 13.133327 38.015995 12.919994 c
37.229328 12.586661 36.582661 12.166661 36.075996 11.659994 c
35.48933 11.073327 35.195995 10.299995 35.195995 9.339994 c
35.195995 8.019995 35.702663 6.946661 36.715996 6.119994 c
37.409328 5.533328 38.382664 5.159994 39.635994 4.999994 c
39.849327 4.973328 40.032661 5.033328 40.185993 5.179994 c
40.339325 5.326661 40.415993 5.499994 40.415993 5.699994 c
40.415993 7.139994 l
40.415993 7.313327 40.362659 7.466661 40.255993 7.599994 c
40.149326 7.733328 40.012661 7.809994 39.845993 7.829994 c
39.679325 7.849994 39.509327 7.886661 39.335995 7.939994 c
38.949326 8.08666 38.675995 8.229994 38.515995 8.369994 c
38.355995 8.509995 38.242664 8.653328 38.175995 8.799995 c
38.109329 9.026661 38.075996 9.239995 38.075996 9.439995 c
38.075996 9.546661 38.135998 9.659995 38.255997 9.779995 c
38.442661 9.966662 38.715996 10.133328 39.075996 10.279995 c
39.235996 10.346662 39.515999 10.446662 39.915997 10.579995 c
42.415997 11.199995 l
42.535995 11.239995 l
43.162663 11.413328 43.615993 11.546661 43.895996 11.639995 c
44.522663 11.853328 45.069328 12.113328 45.535995 12.419994 c
45.535999 12.42 l
h
42.195999 7.9 m
42.035999 7.86 41.905998 7.776667 41.806 7.65 c
41.706001 7.523334 41.656002 7.373334 41.655998 7.2 c
41.655998 5.74 l
41.655998 5.526667 41.742664 5.346667 41.915997 5.2 c
41.982662 5.133334 42.069328 5.09 42.175995 5.07 c
42.282661 5.05 42.38266 5.046667 42.475994 5.06 c
43.649326 5.246667 44.602661 5.646667 45.335995 6.26 c
46.335995 7.086667 46.869328 8.14 46.935993 9.42 c
46.949326 9.606667 46.885994 9.776667 46.745995 9.93 c
46.605995 10.083334 46.435997 10.160001 46.235996 10.16 c
44.675995 10.16 l
44.502663 10.16 44.349331 10.099999 44.215996 9.98 c
44.082661 9.86 44.009327 9.713333 43.995995 9.54 c
43.942661 9.193334 43.829327 8.906667 43.655994 8.68 c
43.375996 8.346667 42.942661 8.093333 42.355995 7.92 c
42.315994 7.92 42.289326 7.913333 42.275993 7.9 c
42.195992 7.9 l
42.195999 7.9 l
h
39.855999 17.08 m
40.015999 17.106667 40.149334 17.186666 40.256001 17.32 c
40.362667 17.453333 40.416 17.599998 40.416 17.76 c
40.416 19.24 l
40.416 19.453333 40.335999 19.633333 40.175999 19.780001 c
40.042664 19.886667 39.889332 19.940001 39.716 19.940001 c
39.616001 19.940001 l
38.375999 19.753334 37.355999 19.346666 36.556 18.720001 c
35.515999 17.893335 34.929333 16.740002 34.796001 15.260001 c
34.769333 15.046668 34.829334 14.863335 34.976002 14.710001 c
35.122669 14.556667 35.296001 14.480001 35.496002 14.480001 c
37.076004 14.480001 l
37.262669 14.480001 37.422668 14.543335 37.556004 14.670001 c
37.689339 14.796667 37.762672 14.953334 37.776005 15.140001 c
37.816006 15.806668 38.142673 16.320002 38.756004 16.68 c
39.036003 16.84 39.402668 16.973333 39.856003 17.08 c
39.855999 17.08 l
h
60.883999 11.12 m
61.084 11.12 61.253998 11.19 61.393997 11.33 c
61.533997 11.47 61.603996 11.64 61.603996 11.84 c
61.603996 13.32 l
61.603996 13.506666 61.533997 13.669999 61.393997 13.81 c
61.253998 13.95 61.084 14.02 60.883999 14.02 c
54.304001 14.02 l
54.304001 19.139999 l
54.304001 19.326666 54.237335 19.49 54.104 19.629999 c
53.970665 19.769999 53.804001 19.839998 53.604 19.839998 c
52.084 19.839998 l
51.897335 19.839998 51.734001 19.769999 51.593998 19.629999 c
51.453995 19.49 51.383995 19.326666 51.383999 19.139999 c
51.383999 11.839999 l
51.383999 11.639999 51.453999 11.469999 51.593998 11.329999 c
51.733997 11.189999 51.897331 11.119999 52.084 11.119999 c
60.883999 11.119999 l
60.883999 11.12 l
h
61.784 5 m
61.970665 5 62.133999 5.07 62.274002 5.21 c
62.414005 5.35 62.484005 5.513333 62.484001 5.7 c
62.484001 7.16 l
62.484001 7.36 62.414001 7.53 62.274002 7.67 c
62.134003 7.81 61.970669 7.88 61.784 7.88 c
52.084 7.88 l
51.897335 7.88 51.734001 7.81 51.593998 7.67 c
51.453995 7.53 51.383995 7.36 51.383999 7.16 c
51.383999 5.7 l
51.383999 5.513333 51.453999 5.35 51.593998 5.21 c
51.733997 5.07 51.897331 5.000001 52.084 5 c
61.784 5 l
h
79.512001 18.860001 m
79.605331 19.073334 79.582001 19.290001 79.442001 19.51 c
79.302002 19.73 79.112 19.84 78.872002 19.84 c
65.552002 19.84 l
65.431999 19.84 65.318672 19.809999 65.212006 19.75 c
65.105339 19.690001 65.025345 19.613333 64.972008 19.52 c
64.82534 19.306667 64.805344 19.086668 64.91201 18.860001 c
65.532013 17.400002 l
65.58535 17.266668 65.672012 17.160002 65.792015 17.080002 c
65.912018 17.000002 66.038681 16.960003 66.172012 16.960001 c
75.492012 16.960001 l
72.212013 9.120001 l
69.532013 15.520001 l
69.478676 15.653334 69.395348 15.756667 69.282013 15.830001 c
69.168678 15.903335 69.038681 15.940002 68.892014 15.940001 c
67.212013 15.940001 l
66.958679 15.940001 66.758682 15.833334 66.612015 15.620001 c
66.558678 15.526668 66.525345 15.420001 66.512016 15.300001 c
66.498688 15.180001 66.512016 15.066669 66.552017 14.960001 c
70.612015 5.440001 l
70.665352 5.306667 70.74868 5.200001 70.862015 5.12 c
70.975349 5.04 71.105347 5 71.252014 5.000001 c
73.172012 5.000001 l
73.31868 5.000001 73.448677 5.04 73.562012 5.12 c
73.675346 5.2 73.758675 5.306667 73.812012 5.440001 c
79.512001 18.860001 l
h
92 5.52 m
92.813332 5.826667 93.546669 6.32 94.199997 7 c
95.599998 8.413333 96.299995 10.216666 96.299995 12.41 c
96.299995 14.603334 95.599998 16.413334 94.199997 17.84 c
93.546661 18.52 92.813332 19.013334 92 19.32 c
91.159996 19.666666 90.299995 19.84 89.419998 19.84 c
83.759995 19.84 l
83.573326 19.84 83.409996 19.77 83.269997 19.630001 c
83.129997 19.490002 83.059998 19.326668 83.059998 19.140001 c
83.059998 17.700001 l
83.059998 17.500002 83.129997 17.330002 83.269997 17.190001 c
83.409996 17.049999 83.573326 16.98 83.759995 16.980001 c
89.359993 16.980001 l
90.546661 16.980001 91.509995 16.546669 92.249992 15.680001 c
92.98999 14.813335 93.359993 13.726667 93.359993 12.420001 c
93.359993 11.113335 92.98999 10.026668 92.249992 9.160001 c
91.509995 8.293334 90.546661 7.860001 89.359993 7.860001 c
83.759995 7.860001 l
83.573326 7.860001 83.409996 7.79 83.269997 7.650001 c
83.129997 7.510001 83.059998 7.340001 83.059998 7.14 c
83.059998 5.7 l
83.059998 5.513333 83.129997 5.35 83.269997 5.21 c
83.409996 5.07 83.573326 5 83.759995 5 c
89.419998 5 l
90.299995 5 91.159996 5.173333 92 5.52 c
h
f
Q
Q
Q
showpage
%%PageTrailer
pdfEndPage
%%Trailer
end
%%DocumentSuppliedResources:
%%EOF

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1014 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View File

@ -0,0 +1,40 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="3200"
height="1355.480324331485" viewBox="0 0 3200 1355.480324331485">
<g transform="scale(10) translate(10, 10)">
<defs id="SvgjsDefs1385">
<linearGradient id="SvgjsLinearGradient1390">
<stop id="SvgjsStop1391" stop-color="#905e26" offset="0"/>
<stop id="SvgjsStop1392" stop-color="#f5ec9b" offset="0.5"/>
<stop id="SvgjsStop1393" stop-color="#905e26" offset="1"/>
</linearGradient>
<linearGradient id="SvgjsLinearGradient1394">
<stop id="SvgjsStop1395" stop-color="#905e26" offset="0"/>
<stop id="SvgjsStop1396" stop-color="#f5ec9b" offset="0.5"/>
<stop id="SvgjsStop1397" stop-color="#905e26" offset="1"/>
</linearGradient>
</defs>
<g id="SvgjsG1386" featureKey="aMgJeN-0"
transform="matrix(1.5610770874511997,0,0,1.5610770874511997,71.94613967240352,-53.841545411371435)"
fill="#000">
<path xmlns="http://www.w3.org/2000/svg"
d="M29.399,57.112c0.615,0.308,1.077,0.846,1.077,1.383c0,1.039-1.038,1.961-1.999,1.462l-15.223-7.881 c-0.846-0.499-1.576-0.808-1.576-1.884c0-1.115,0.692-1.385,1.576-1.922l15.223-7.881c1.038-0.346,1.999,0.424,1.999,1.462 c0,0.575-0.461,1.114-1.077,1.423l-13.761,6.918L29.399,57.112z"/>
<path xmlns="http://www.w3.org/2000/svg"
d="M29.033,60.209c-0.208,0-0.413-0.052-0.608-0.152l-15.223-7.881c-0.086-0.05-0.165-0.095-0.242-0.141 c-0.748-0.431-1.395-0.804-1.395-1.843c0-1.057,0.594-1.406,1.346-1.849c0.093-0.054,0.187-0.11,0.284-0.169l15.229-7.885 c0.195-0.066,0.377-0.097,0.558-0.097c0.9,0,1.606,0.728,1.606,1.658c0,0.587-0.437,1.172-1.139,1.522l-13.562,6.818l13.562,6.818 c0.691,0.346,1.139,0.93,1.139,1.484C30.588,59.407,29.861,60.209,29.033,60.209z M28.982,40.419c-0.156,0-0.314,0.026-0.47,0.078 L13.306,48.37c-0.091,0.057-0.188,0.113-0.281,0.167c-0.743,0.439-1.234,0.728-1.234,1.655c0,0.91,0.537,1.219,1.281,1.648 c0.079,0.045,0.158,0.09,0.239,0.139l15.217,7.877c0.162,0.084,0.332,0.127,0.504,0.127c0.697,0,1.33-0.709,1.33-1.488 c0-0.465-0.407-0.98-1.014-1.283l-13.962-7.02l13.962-7.02c0.616-0.308,1.014-0.828,1.014-1.321 C30.363,41.048,29.756,40.419,28.982,40.419z"/>
<path xmlns="http://www.w3.org/2000/svg"
d="M46.385,64.416c-0.231,0.691-0.922,1.077-1.614,0.961c-0.769-0.153-1.269-0.885-1.154-1.692 c0-0.076,0.039-0.191,0.077-0.307l9.88-27.831c0.23-0.692,0.922-1.038,1.614-0.923c0.73,0.154,1.269,0.885,1.153,1.652 c0,0.078-0.039,0.193-0.077,0.27L46.385,64.416z"/>
<path xmlns="http://www.w3.org/2000/svg"
d="M45.016,65.511c-0.088,0-0.177-0.008-0.263-0.022c-0.837-0.167-1.371-0.949-1.247-1.819 c-0.001-0.076,0.038-0.193,0.079-0.318l9.883-27.842c0.207-0.618,0.778-1.02,1.457-1.02c0.094,0,0.188,0.009,0.281,0.023 c0.812,0.172,1.369,0.97,1.247,1.781c0.001,0.086-0.047,0.22-0.087,0.302l-9.875,27.856C46.281,65.084,45.688,65.511,45.016,65.511z M54.925,34.715c-0.58,0-1.068,0.34-1.244,0.867l-9.88,27.833c-0.035,0.104-0.07,0.213-0.07,0.27 c-0.108,0.767,0.349,1.439,1.062,1.582c0.071,0.012,0.147,0.018,0.223,0.018c0.575,0,1.083-0.363,1.263-0.904l9.881-27.872 c0.041-0.085,0.07-0.182,0.07-0.231c0.105-0.712-0.373-1.396-1.064-1.543C55.089,34.722,55.007,34.715,54.925,34.715z"/>
<path xmlns="http://www.w3.org/2000/svg"
d="M84.362,50.192L70.6,43.274c-0.614-0.309-1.075-0.848-1.075-1.423c0-1.038,1.037-1.962,1.998-1.462l15.223,7.881 c0.885,0.537,1.576,0.807,1.576,1.922c0,1.076-0.73,1.385-1.576,1.884l-15.223,7.881c-0.961,0.499-1.998-0.423-1.998-1.462 c0-0.537,0.461-1.075,1.075-1.383L84.362,50.192z"/>
<path xmlns="http://www.w3.org/2000/svg"
d="M70.967,60.209c-0.828,0-1.556-0.802-1.556-1.714c0-0.555,0.447-1.139,1.139-1.484l13.562-6.818L70.55,43.374 c-0.702-0.352-1.139-0.936-1.139-1.522c0-0.913,0.728-1.714,1.556-1.714c0.209,0,0.413,0.051,0.608,0.152l15.223,7.881 c0.104,0.062,0.198,0.119,0.29,0.173c0.753,0.442,1.347,0.792,1.347,1.849c0,1.039-0.646,1.412-1.395,1.843 c-0.078,0.046-0.157,0.091-0.237,0.138l-15.228,7.884C71.38,60.157,71.176,60.209,70.967,60.209z M70.967,40.363 c-0.696,0-1.33,0.709-1.33,1.488c0,0.493,0.397,1.013,1.014,1.321l13.962,7.02l-13.962,7.02c-0.606,0.305-1.014,0.82-1.014,1.283 c0,0.779,0.634,1.488,1.33,1.488c0.172,0,0.342-0.043,0.504-0.127l15.223-7.88c0.075-0.046,0.155-0.091,0.233-0.136 c0.744-0.43,1.282-0.738,1.282-1.648c0-0.928-0.491-1.216-1.235-1.655c-0.093-0.054-0.188-0.11-0.287-0.17l-15.216-7.876 C71.309,40.405,71.139,40.363,70.967,40.363z"/>
</g>
<g id="SvgjsG1387" featureKey="8L6ael-0"
transform="matrix(3.168568052463937,0,0,3.168568052463937,-5.1330821487142195,52.17667742743372)"
fill="#000">
<path d="M10.56 5.52 q1.22 0.46 2.2 1.48 q2.1 2.12 2.1 5.41 t-2.1 5.43 q-0.98 1.02 -2.2 1.48 q-1.26 0.52 -2.58 0.52 l-5.66 0 q-0.28 0 -0.49 -0.21 t-0.21 -0.49 l0 -1.44 q0 -0.3 0.21 -0.51 t0.49 -0.21 l5.6 0 q1.78 0 2.89 -1.3 t1.11 -3.26 t-1.11 -3.26 t-2.89 -1.3 l-5.6 0 q-0.28 0 -0.49 -0.21 t-0.21 -0.51 l0 -1.44 q0 -0.28 0.21 -0.49 t0.49 -0.21 l5.66 0 q1.32 0 2.58 0.52 z M32.208 18.86 q0.14 0.32 -0.07 0.65 t-0.57 0.33 l-13.32 0 q-0.18 0 -0.34 -0.09 t-0.24 -0.23 q-0.22 -0.32 -0.06 -0.66 l0.62 -1.46 q0.08 -0.2 0.26 -0.32 t0.38 -0.12 l9.32 0 l-3.28 -7.84 l-2.68 6.4 q-0.08 0.2 -0.25 0.31 t-0.39 0.11 l-1.68 0 q-0.38 0 -0.6 -0.32 q-0.08 -0.14 -0.1 -0.32 t0.04 -0.34 l4.06 -9.52 q0.08 -0.2 0.25 -0.32 t0.39 -0.12 l1.92 0 q0.22 0 0.39 0.12 t0.25 0.32 z M45.535999999999994 12.42 q0.86 0.54 1.31 1.32 t0.45 1.74 q0 2.26 -1.72 3.46 q-1.16 0.84 -3.14 1.06 l-0.08 0 q-0.28 0 -0.46 -0.18 q-0.24 -0.22 -0.24 -0.52 l0 -1.44 q0 -0.26 0.18 -0.46 t0.44 -0.24 q0.94 -0.1 1.46 -0.44 q0.4 -0.24 0.54 -0.62 q0.08 -0.22 0.08 -0.56 q0 -0.22 -0.08 -0.38 t-0.28 -0.3 q-0.58 -0.4 -1.5 -0.68 l-0.32 -0.1 q-1 -0.28 -1.84 -0.46 q-0.18 -0.04 -0.58 -0.14 l-0.22 -0.06 q-0.72 -0.18 -1.52 -0.5 q-1.18 -0.5 -1.94 -1.26 q-0.88 -0.88 -0.88 -2.32 q0 -1.98 1.52 -3.22 q1.04 -0.88 2.92 -1.12 q0.32 -0.04 0.55 0.18 t0.23 0.52 l0 1.44 q0 0.26 -0.16 0.46 t-0.41 0.23 t-0.51 0.11 q-0.58 0.22 -0.82 0.43 t-0.34 0.43 q-0.1 0.34 -0.1 0.64 q0 0.16 0.18 0.34 q0.28 0.28 0.82 0.5 q0.24 0.1 0.84 0.3 l2.5 0.62 l0.12 0.04 q0.94 0.26 1.36 0.4 q0.94 0.32 1.64 0.78 z M42.196 7.9 q-0.24 -0.06 -0.39 -0.25 t-0.15 -0.45 l0 -1.46 q0 -0.32 0.26 -0.54 q0.1 -0.1 0.26 -0.13 t0.3 -0.01 q1.76 0.28 2.86 1.2 q1.5 1.24 1.6 3.16 q0.02 0.28 -0.19 0.51 t-0.51 0.23 l-1.56 0 q-0.26 0 -0.46 -0.18 t-0.22 -0.44 q-0.08 -0.52 -0.34 -0.86 q-0.42 -0.5 -1.3 -0.76 q-0.06 0 -0.08 -0.02 l-0.08 0 z M39.855999999999995 17.08 q0.24 0.04 0.4 0.24 t0.16 0.44 l0 1.48 q0 0.32 -0.24 0.54 q-0.2 0.16 -0.46 0.16 l-0.1 0 q-1.86 -0.28 -3.06 -1.22 q-1.56 -1.24 -1.76 -3.46 q-0.04 -0.32 0.18 -0.55 t0.52 -0.23 l1.58 0 q0.28 0 0.48 0.19 t0.22 0.47 q0.06 1 0.98 1.54 q0.42 0.24 1.1 0.4 z M60.88399999999999 11.12 q0.3 0 0.51 0.21 t0.21 0.51 l0 1.48 q0 0.28 -0.21 0.49 t-0.51 0.21 l-6.58 0 l0 5.12 q0 0.28 -0.2 0.49 t-0.5 0.21 l-1.52 0 q-0.28 0 -0.49 -0.21 t-0.21 -0.49 l0 -7.3 q0 -0.3 0.21 -0.51 t0.49 -0.21 l8.8 0 z M61.78399999999999 5 q0.28 0 0.49 0.21 t0.21 0.49 l0 1.46 q0 0.3 -0.21 0.51 t-0.49 0.21 l-9.7 0 q-0.28 0 -0.49 -0.21 t-0.21 -0.51 l0 -1.46 q0 -0.28 0.21 -0.49 t0.49 -0.21 l9.7 0 z M79.512 18.86 q0.14 0.32 -0.07 0.65 t-0.57 0.33 l-13.32 0 q-0.18 0 -0.34 -0.09 t-0.24 -0.23 q-0.22 -0.32 -0.06 -0.66 l0.62 -1.46 q0.08 -0.2 0.26 -0.32 t0.38 -0.12 l9.32 0 l-3.28 -7.84 l-2.68 6.4 q-0.08 0.2 -0.25 0.31 t-0.39 0.11 l-1.68 0 q-0.38 0 -0.6 -0.32 q-0.08 -0.14 -0.1 -0.32 t0.04 -0.34 l4.06 -9.52 q0.08 -0.2 0.25 -0.32 t0.39 -0.12 l1.92 0 q0.22 0 0.39 0.12 t0.25 0.32 z M92 5.52 q1.22 0.46 2.2 1.48 q2.1 2.12 2.1 5.41 t-2.1 5.43 q-0.98 1.02 -2.2 1.48 q-1.26 0.52 -2.58 0.52 l-5.66 0 q-0.28 0 -0.49 -0.21 t-0.21 -0.49 l0 -1.44 q0 -0.3 0.21 -0.51 t0.49 -0.21 l5.6 0 q1.78 0 2.89 -1.3 t1.11 -3.26 t-1.11 -3.26 t-2.89 -1.3 l-5.6 0 q-0.28 0 -0.49 -0.21 t-0.21 -0.51 l0 -1.44 q0 -0.28 0.21 -0.49 t0.49 -0.21 l5.66 0 q1.32 0 2.58 0.52 z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@ -0,0 +1,40 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="3200"
height="1355.480324331485" viewBox="0 0 3200 1355.480324331485">
<g transform="scale(10) translate(10, 10)">
<defs id="SvgjsDefs1385">
<linearGradient id="SvgjsLinearGradient1390">
<stop id="SvgjsStop1391" stop-color="#905e26" offset="0"></stop>
<stop id="SvgjsStop1392" stop-color="#f5ec9b" offset="0.5"></stop>
<stop id="SvgjsStop1393" stop-color="#905e26" offset="1"></stop>
</linearGradient>
<linearGradient id="SvgjsLinearGradient1394">
<stop id="SvgjsStop1395" stop-color="#905e26" offset="0"></stop>
<stop id="SvgjsStop1396" stop-color="#f5ec9b" offset="0.5"></stop>
<stop id="SvgjsStop1397" stop-color="#905e26" offset="1"></stop>
</linearGradient>
</defs>
<g id="SvgjsG1386" featureKey="aMgJeN-0"
transform="matrix(1.5610770874511997,0,0,1.5610770874511997,71.94613967240352,-53.841545411371435)"
fill="url(#SvgjsLinearGradient1390)">
<path xmlns="http://www.w3.org/2000/svg"
d="M29.399,57.112c0.615,0.308,1.077,0.846,1.077,1.383c0,1.039-1.038,1.961-1.999,1.462l-15.223-7.881 c-0.846-0.499-1.576-0.808-1.576-1.884c0-1.115,0.692-1.385,1.576-1.922l15.223-7.881c1.038-0.346,1.999,0.424,1.999,1.462 c0,0.575-0.461,1.114-1.077,1.423l-13.761,6.918L29.399,57.112z"></path>
<path xmlns="http://www.w3.org/2000/svg"
d="M29.033,60.209c-0.208,0-0.413-0.052-0.608-0.152l-15.223-7.881c-0.086-0.05-0.165-0.095-0.242-0.141 c-0.748-0.431-1.395-0.804-1.395-1.843c0-1.057,0.594-1.406,1.346-1.849c0.093-0.054,0.187-0.11,0.284-0.169l15.229-7.885 c0.195-0.066,0.377-0.097,0.558-0.097c0.9,0,1.606,0.728,1.606,1.658c0,0.587-0.437,1.172-1.139,1.522l-13.562,6.818l13.562,6.818 c0.691,0.346,1.139,0.93,1.139,1.484C30.588,59.407,29.861,60.209,29.033,60.209z M28.982,40.419c-0.156,0-0.314,0.026-0.47,0.078 L13.306,48.37c-0.091,0.057-0.188,0.113-0.281,0.167c-0.743,0.439-1.234,0.728-1.234,1.655c0,0.91,0.537,1.219,1.281,1.648 c0.079,0.045,0.158,0.09,0.239,0.139l15.217,7.877c0.162,0.084,0.332,0.127,0.504,0.127c0.697,0,1.33-0.709,1.33-1.488 c0-0.465-0.407-0.98-1.014-1.283l-13.962-7.02l13.962-7.02c0.616-0.308,1.014-0.828,1.014-1.321 C30.363,41.048,29.756,40.419,28.982,40.419z"></path>
<path xmlns="http://www.w3.org/2000/svg"
d="M46.385,64.416c-0.231,0.691-0.922,1.077-1.614,0.961c-0.769-0.153-1.269-0.885-1.154-1.692 c0-0.076,0.039-0.191,0.077-0.307l9.88-27.831c0.23-0.692,0.922-1.038,1.614-0.923c0.73,0.154,1.269,0.885,1.153,1.652 c0,0.078-0.039,0.193-0.077,0.27L46.385,64.416z"></path>
<path xmlns="http://www.w3.org/2000/svg"
d="M45.016,65.511c-0.088,0-0.177-0.008-0.263-0.022c-0.837-0.167-1.371-0.949-1.247-1.819 c-0.001-0.076,0.038-0.193,0.079-0.318l9.883-27.842c0.207-0.618,0.778-1.02,1.457-1.02c0.094,0,0.188,0.009,0.281,0.023 c0.812,0.172,1.369,0.97,1.247,1.781c0.001,0.086-0.047,0.22-0.087,0.302l-9.875,27.856C46.281,65.084,45.688,65.511,45.016,65.511z M54.925,34.715c-0.58,0-1.068,0.34-1.244,0.867l-9.88,27.833c-0.035,0.104-0.07,0.213-0.07,0.27 c-0.108,0.767,0.349,1.439,1.062,1.582c0.071,0.012,0.147,0.018,0.223,0.018c0.575,0,1.083-0.363,1.263-0.904l9.881-27.872 c0.041-0.085,0.07-0.182,0.07-0.231c0.105-0.712-0.373-1.396-1.064-1.543C55.089,34.722,55.007,34.715,54.925,34.715z"></path>
<path xmlns="http://www.w3.org/2000/svg"
d="M84.362,50.192L70.6,43.274c-0.614-0.309-1.075-0.848-1.075-1.423c0-1.038,1.037-1.962,1.998-1.462l15.223,7.881 c0.885,0.537,1.576,0.807,1.576,1.922c0,1.076-0.73,1.385-1.576,1.884l-15.223,7.881c-0.961,0.499-1.998-0.423-1.998-1.462 c0-0.537,0.461-1.075,1.075-1.383L84.362,50.192z"></path>
<path xmlns="http://www.w3.org/2000/svg"
d="M70.967,60.209c-0.828,0-1.556-0.802-1.556-1.714c0-0.555,0.447-1.139,1.139-1.484l13.562-6.818L70.55,43.374 c-0.702-0.352-1.139-0.936-1.139-1.522c0-0.913,0.728-1.714,1.556-1.714c0.209,0,0.413,0.051,0.608,0.152l15.223,7.881 c0.104,0.062,0.198,0.119,0.29,0.173c0.753,0.442,1.347,0.792,1.347,1.849c0,1.039-0.646,1.412-1.395,1.843 c-0.078,0.046-0.157,0.091-0.237,0.138l-15.228,7.884C71.38,60.157,71.176,60.209,70.967,60.209z M70.967,40.363 c-0.696,0-1.33,0.709-1.33,1.488c0,0.493,0.397,1.013,1.014,1.321l13.962,7.02l-13.962,7.02c-0.606,0.305-1.014,0.82-1.014,1.283 c0,0.779,0.634,1.488,1.33,1.488c0.172,0,0.342-0.043,0.504-0.127l15.223-7.88c0.075-0.046,0.155-0.091,0.233-0.136 c0.744-0.43,1.282-0.738,1.282-1.648c0-0.928-0.491-1.216-1.235-1.655c-0.093-0.054-0.188-0.11-0.287-0.17l-15.216-7.876 C71.309,40.405,71.139,40.363,70.967,40.363z"></path>
</g>
<g id="SvgjsG1387" featureKey="8L6ael-0"
transform="matrix(3.168568052463937,0,0,3.168568052463937,-5.1330821487142195,52.17667742743372)"
fill="url(#SvgjsLinearGradient1394)">
<path d="M10.56 5.52 q1.22 0.46 2.2 1.48 q2.1 2.12 2.1 5.41 t-2.1 5.43 q-0.98 1.02 -2.2 1.48 q-1.26 0.52 -2.58 0.52 l-5.66 0 q-0.28 0 -0.49 -0.21 t-0.21 -0.49 l0 -1.44 q0 -0.3 0.21 -0.51 t0.49 -0.21 l5.6 0 q1.78 0 2.89 -1.3 t1.11 -3.26 t-1.11 -3.26 t-2.89 -1.3 l-5.6 0 q-0.28 0 -0.49 -0.21 t-0.21 -0.51 l0 -1.44 q0 -0.28 0.21 -0.49 t0.49 -0.21 l5.66 0 q1.32 0 2.58 0.52 z M32.208 18.86 q0.14 0.32 -0.07 0.65 t-0.57 0.33 l-13.32 0 q-0.18 0 -0.34 -0.09 t-0.24 -0.23 q-0.22 -0.32 -0.06 -0.66 l0.62 -1.46 q0.08 -0.2 0.26 -0.32 t0.38 -0.12 l9.32 0 l-3.28 -7.84 l-2.68 6.4 q-0.08 0.2 -0.25 0.31 t-0.39 0.11 l-1.68 0 q-0.38 0 -0.6 -0.32 q-0.08 -0.14 -0.1 -0.32 t0.04 -0.34 l4.06 -9.52 q0.08 -0.2 0.25 -0.32 t0.39 -0.12 l1.92 0 q0.22 0 0.39 0.12 t0.25 0.32 z M45.535999999999994 12.42 q0.86 0.54 1.31 1.32 t0.45 1.74 q0 2.26 -1.72 3.46 q-1.16 0.84 -3.14 1.06 l-0.08 0 q-0.28 0 -0.46 -0.18 q-0.24 -0.22 -0.24 -0.52 l0 -1.44 q0 -0.26 0.18 -0.46 t0.44 -0.24 q0.94 -0.1 1.46 -0.44 q0.4 -0.24 0.54 -0.62 q0.08 -0.22 0.08 -0.56 q0 -0.22 -0.08 -0.38 t-0.28 -0.3 q-0.58 -0.4 -1.5 -0.68 l-0.32 -0.1 q-1 -0.28 -1.84 -0.46 q-0.18 -0.04 -0.58 -0.14 l-0.22 -0.06 q-0.72 -0.18 -1.52 -0.5 q-1.18 -0.5 -1.94 -1.26 q-0.88 -0.88 -0.88 -2.32 q0 -1.98 1.52 -3.22 q1.04 -0.88 2.92 -1.12 q0.32 -0.04 0.55 0.18 t0.23 0.52 l0 1.44 q0 0.26 -0.16 0.46 t-0.41 0.23 t-0.51 0.11 q-0.58 0.22 -0.82 0.43 t-0.34 0.43 q-0.1 0.34 -0.1 0.64 q0 0.16 0.18 0.34 q0.28 0.28 0.82 0.5 q0.24 0.1 0.84 0.3 l2.5 0.62 l0.12 0.04 q0.94 0.26 1.36 0.4 q0.94 0.32 1.64 0.78 z M42.196 7.9 q-0.24 -0.06 -0.39 -0.25 t-0.15 -0.45 l0 -1.46 q0 -0.32 0.26 -0.54 q0.1 -0.1 0.26 -0.13 t0.3 -0.01 q1.76 0.28 2.86 1.2 q1.5 1.24 1.6 3.16 q0.02 0.28 -0.19 0.51 t-0.51 0.23 l-1.56 0 q-0.26 0 -0.46 -0.18 t-0.22 -0.44 q-0.08 -0.52 -0.34 -0.86 q-0.42 -0.5 -1.3 -0.76 q-0.06 0 -0.08 -0.02 l-0.08 0 z M39.855999999999995 17.08 q0.24 0.04 0.4 0.24 t0.16 0.44 l0 1.48 q0 0.32 -0.24 0.54 q-0.2 0.16 -0.46 0.16 l-0.1 0 q-1.86 -0.28 -3.06 -1.22 q-1.56 -1.24 -1.76 -3.46 q-0.04 -0.32 0.18 -0.55 t0.52 -0.23 l1.58 0 q0.28 0 0.48 0.19 t0.22 0.47 q0.06 1 0.98 1.54 q0.42 0.24 1.1 0.4 z M60.88399999999999 11.12 q0.3 0 0.51 0.21 t0.21 0.51 l0 1.48 q0 0.28 -0.21 0.49 t-0.51 0.21 l-6.58 0 l0 5.12 q0 0.28 -0.2 0.49 t-0.5 0.21 l-1.52 0 q-0.28 0 -0.49 -0.21 t-0.21 -0.49 l0 -7.3 q0 -0.3 0.21 -0.51 t0.49 -0.21 l8.8 0 z M61.78399999999999 5 q0.28 0 0.49 0.21 t0.21 0.49 l0 1.46 q0 0.3 -0.21 0.51 t-0.49 0.21 l-9.7 0 q-0.28 0 -0.49 -0.21 t-0.21 -0.51 l0 -1.46 q0 -0.28 0.21 -0.49 t0.49 -0.21 l9.7 0 z M79.512 18.86 q0.14 0.32 -0.07 0.65 t-0.57 0.33 l-13.32 0 q-0.18 0 -0.34 -0.09 t-0.24 -0.23 q-0.22 -0.32 -0.06 -0.66 l0.62 -1.46 q0.08 -0.2 0.26 -0.32 t0.38 -0.12 l9.32 0 l-3.28 -7.84 l-2.68 6.4 q-0.08 0.2 -0.25 0.31 t-0.39 0.11 l-1.68 0 q-0.38 0 -0.6 -0.32 q-0.08 -0.14 -0.1 -0.32 t0.04 -0.34 l4.06 -9.52 q0.08 -0.2 0.25 -0.32 t0.39 -0.12 l1.92 0 q0.22 0 0.39 0.12 t0.25 0.32 z M92 5.52 q1.22 0.46 2.2 1.48 q2.1 2.12 2.1 5.41 t-2.1 5.43 q-0.98 1.02 -2.2 1.48 q-1.26 0.52 -2.58 0.52 l-5.66 0 q-0.28 0 -0.49 -0.21 t-0.21 -0.49 l0 -1.44 q0 -0.3 0.21 -0.51 t0.49 -0.21 l5.6 0 q1.78 0 2.89 -1.3 t1.11 -3.26 t-1.11 -3.26 t-2.89 -1.3 l-5.6 0 q-0.28 0 -0.49 -0.21 t-0.21 -0.51 l0 -1.44 q0 -0.28 0.21 -0.49 t0.49 -0.21 l5.66 0 q1.32 0 2.58 0.52 z"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@ -0,0 +1,40 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="3200"
height="1355.480324331485" viewBox="0 0 3200 1355.480324331485">
<rect fill="#292929" width="3200" height="1355.480324331485"/>
<g transform="scale(10) translate(10, 10)">
<defs id="SvgjsDefs1385">
<linearGradient id="SvgjsLinearGradient1390">
<stop id="SvgjsStop1391" stop-color="#905e26" offset="0"></stop>
<stop id="SvgjsStop1392" stop-color="#f5ec9b" offset="0.5"></stop>
<stop id="SvgjsStop1393" stop-color="#905e26" offset="1"></stop>
</linearGradient>
<linearGradient id="SvgjsLinearGradient1394">
<stop id="SvgjsStop1395" stop-color="#905e26" offset="0"></stop>
<stop id="SvgjsStop1396" stop-color="#f5ec9b" offset="0.5"></stop>
<stop id="SvgjsStop1397" stop-color="#905e26" offset="1"></stop>
</linearGradient>
</defs>
<g id="SvgjsG1386" featureKey="aMgJeN-0"
transform="matrix(1.5610770874511997,0,0,1.5610770874511997,71.94613967240352,-53.841545411371435)"
fill="url(#SvgjsLinearGradient1390)">
<path xmlns="http://www.w3.org/2000/svg"
d="M29.399,57.112c0.615,0.308,1.077,0.846,1.077,1.383c0,1.039-1.038,1.961-1.999,1.462l-15.223-7.881 c-0.846-0.499-1.576-0.808-1.576-1.884c0-1.115,0.692-1.385,1.576-1.922l15.223-7.881c1.038-0.346,1.999,0.424,1.999,1.462 c0,0.575-0.461,1.114-1.077,1.423l-13.761,6.918L29.399,57.112z"></path>
<path xmlns="http://www.w3.org/2000/svg"
d="M29.033,60.209c-0.208,0-0.413-0.052-0.608-0.152l-15.223-7.881c-0.086-0.05-0.165-0.095-0.242-0.141 c-0.748-0.431-1.395-0.804-1.395-1.843c0-1.057,0.594-1.406,1.346-1.849c0.093-0.054,0.187-0.11,0.284-0.169l15.229-7.885 c0.195-0.066,0.377-0.097,0.558-0.097c0.9,0,1.606,0.728,1.606,1.658c0,0.587-0.437,1.172-1.139,1.522l-13.562,6.818l13.562,6.818 c0.691,0.346,1.139,0.93,1.139,1.484C30.588,59.407,29.861,60.209,29.033,60.209z M28.982,40.419c-0.156,0-0.314,0.026-0.47,0.078 L13.306,48.37c-0.091,0.057-0.188,0.113-0.281,0.167c-0.743,0.439-1.234,0.728-1.234,1.655c0,0.91,0.537,1.219,1.281,1.648 c0.079,0.045,0.158,0.09,0.239,0.139l15.217,7.877c0.162,0.084,0.332,0.127,0.504,0.127c0.697,0,1.33-0.709,1.33-1.488 c0-0.465-0.407-0.98-1.014-1.283l-13.962-7.02l13.962-7.02c0.616-0.308,1.014-0.828,1.014-1.321 C30.363,41.048,29.756,40.419,28.982,40.419z"></path>
<path xmlns="http://www.w3.org/2000/svg"
d="M46.385,64.416c-0.231,0.691-0.922,1.077-1.614,0.961c-0.769-0.153-1.269-0.885-1.154-1.692 c0-0.076,0.039-0.191,0.077-0.307l9.88-27.831c0.23-0.692,0.922-1.038,1.614-0.923c0.73,0.154,1.269,0.885,1.153,1.652 c0,0.078-0.039,0.193-0.077,0.27L46.385,64.416z"></path>
<path xmlns="http://www.w3.org/2000/svg"
d="M45.016,65.511c-0.088,0-0.177-0.008-0.263-0.022c-0.837-0.167-1.371-0.949-1.247-1.819 c-0.001-0.076,0.038-0.193,0.079-0.318l9.883-27.842c0.207-0.618,0.778-1.02,1.457-1.02c0.094,0,0.188,0.009,0.281,0.023 c0.812,0.172,1.369,0.97,1.247,1.781c0.001,0.086-0.047,0.22-0.087,0.302l-9.875,27.856C46.281,65.084,45.688,65.511,45.016,65.511z M54.925,34.715c-0.58,0-1.068,0.34-1.244,0.867l-9.88,27.833c-0.035,0.104-0.07,0.213-0.07,0.27 c-0.108,0.767,0.349,1.439,1.062,1.582c0.071,0.012,0.147,0.018,0.223,0.018c0.575,0,1.083-0.363,1.263-0.904l9.881-27.872 c0.041-0.085,0.07-0.182,0.07-0.231c0.105-0.712-0.373-1.396-1.064-1.543C55.089,34.722,55.007,34.715,54.925,34.715z"></path>
<path xmlns="http://www.w3.org/2000/svg"
d="M84.362,50.192L70.6,43.274c-0.614-0.309-1.075-0.848-1.075-1.423c0-1.038,1.037-1.962,1.998-1.462l15.223,7.881 c0.885,0.537,1.576,0.807,1.576,1.922c0,1.076-0.73,1.385-1.576,1.884l-15.223,7.881c-0.961,0.499-1.998-0.423-1.998-1.462 c0-0.537,0.461-1.075,1.075-1.383L84.362,50.192z"></path>
<path xmlns="http://www.w3.org/2000/svg"
d="M70.967,60.209c-0.828,0-1.556-0.802-1.556-1.714c0-0.555,0.447-1.139,1.139-1.484l13.562-6.818L70.55,43.374 c-0.702-0.352-1.139-0.936-1.139-1.522c0-0.913,0.728-1.714,1.556-1.714c0.209,0,0.413,0.051,0.608,0.152l15.223,7.881 c0.104,0.062,0.198,0.119,0.29,0.173c0.753,0.442,1.347,0.792,1.347,1.849c0,1.039-0.646,1.412-1.395,1.843 c-0.078,0.046-0.157,0.091-0.237,0.138l-15.228,7.884C71.38,60.157,71.176,60.209,70.967,60.209z M70.967,40.363 c-0.696,0-1.33,0.709-1.33,1.488c0,0.493,0.397,1.013,1.014,1.321l13.962,7.02l-13.962,7.02c-0.606,0.305-1.014,0.82-1.014,1.283 c0,0.779,0.634,1.488,1.33,1.488c0.172,0,0.342-0.043,0.504-0.127l15.223-7.88c0.075-0.046,0.155-0.091,0.233-0.136 c0.744-0.43,1.282-0.738,1.282-1.648c0-0.928-0.491-1.216-1.235-1.655c-0.093-0.054-0.188-0.11-0.287-0.17l-15.216-7.876 C71.309,40.405,71.139,40.363,70.967,40.363z"></path>
</g>
<g id="SvgjsG1387" featureKey="8L6ael-0"
transform="matrix(3.168568052463937,0,0,3.168568052463937,-5.1330821487142195,52.17667742743372)"
fill="url(#SvgjsLinearGradient1394)">
<path d="M10.56 5.52 q1.22 0.46 2.2 1.48 q2.1 2.12 2.1 5.41 t-2.1 5.43 q-0.98 1.02 -2.2 1.48 q-1.26 0.52 -2.58 0.52 l-5.66 0 q-0.28 0 -0.49 -0.21 t-0.21 -0.49 l0 -1.44 q0 -0.3 0.21 -0.51 t0.49 -0.21 l5.6 0 q1.78 0 2.89 -1.3 t1.11 -3.26 t-1.11 -3.26 t-2.89 -1.3 l-5.6 0 q-0.28 0 -0.49 -0.21 t-0.21 -0.51 l0 -1.44 q0 -0.28 0.21 -0.49 t0.49 -0.21 l5.66 0 q1.32 0 2.58 0.52 z M32.208 18.86 q0.14 0.32 -0.07 0.65 t-0.57 0.33 l-13.32 0 q-0.18 0 -0.34 -0.09 t-0.24 -0.23 q-0.22 -0.32 -0.06 -0.66 l0.62 -1.46 q0.08 -0.2 0.26 -0.32 t0.38 -0.12 l9.32 0 l-3.28 -7.84 l-2.68 6.4 q-0.08 0.2 -0.25 0.31 t-0.39 0.11 l-1.68 0 q-0.38 0 -0.6 -0.32 q-0.08 -0.14 -0.1 -0.32 t0.04 -0.34 l4.06 -9.52 q0.08 -0.2 0.25 -0.32 t0.39 -0.12 l1.92 0 q0.22 0 0.39 0.12 t0.25 0.32 z M45.535999999999994 12.42 q0.86 0.54 1.31 1.32 t0.45 1.74 q0 2.26 -1.72 3.46 q-1.16 0.84 -3.14 1.06 l-0.08 0 q-0.28 0 -0.46 -0.18 q-0.24 -0.22 -0.24 -0.52 l0 -1.44 q0 -0.26 0.18 -0.46 t0.44 -0.24 q0.94 -0.1 1.46 -0.44 q0.4 -0.24 0.54 -0.62 q0.08 -0.22 0.08 -0.56 q0 -0.22 -0.08 -0.38 t-0.28 -0.3 q-0.58 -0.4 -1.5 -0.68 l-0.32 -0.1 q-1 -0.28 -1.84 -0.46 q-0.18 -0.04 -0.58 -0.14 l-0.22 -0.06 q-0.72 -0.18 -1.52 -0.5 q-1.18 -0.5 -1.94 -1.26 q-0.88 -0.88 -0.88 -2.32 q0 -1.98 1.52 -3.22 q1.04 -0.88 2.92 -1.12 q0.32 -0.04 0.55 0.18 t0.23 0.52 l0 1.44 q0 0.26 -0.16 0.46 t-0.41 0.23 t-0.51 0.11 q-0.58 0.22 -0.82 0.43 t-0.34 0.43 q-0.1 0.34 -0.1 0.64 q0 0.16 0.18 0.34 q0.28 0.28 0.82 0.5 q0.24 0.1 0.84 0.3 l2.5 0.62 l0.12 0.04 q0.94 0.26 1.36 0.4 q0.94 0.32 1.64 0.78 z M42.196 7.9 q-0.24 -0.06 -0.39 -0.25 t-0.15 -0.45 l0 -1.46 q0 -0.32 0.26 -0.54 q0.1 -0.1 0.26 -0.13 t0.3 -0.01 q1.76 0.28 2.86 1.2 q1.5 1.24 1.6 3.16 q0.02 0.28 -0.19 0.51 t-0.51 0.23 l-1.56 0 q-0.26 0 -0.46 -0.18 t-0.22 -0.44 q-0.08 -0.52 -0.34 -0.86 q-0.42 -0.5 -1.3 -0.76 q-0.06 0 -0.08 -0.02 l-0.08 0 z M39.855999999999995 17.08 q0.24 0.04 0.4 0.24 t0.16 0.44 l0 1.48 q0 0.32 -0.24 0.54 q-0.2 0.16 -0.46 0.16 l-0.1 0 q-1.86 -0.28 -3.06 -1.22 q-1.56 -1.24 -1.76 -3.46 q-0.04 -0.32 0.18 -0.55 t0.52 -0.23 l1.58 0 q0.28 0 0.48 0.19 t0.22 0.47 q0.06 1 0.98 1.54 q0.42 0.24 1.1 0.4 z M60.88399999999999 11.12 q0.3 0 0.51 0.21 t0.21 0.51 l0 1.48 q0 0.28 -0.21 0.49 t-0.51 0.21 l-6.58 0 l0 5.12 q0 0.28 -0.2 0.49 t-0.5 0.21 l-1.52 0 q-0.28 0 -0.49 -0.21 t-0.21 -0.49 l0 -7.3 q0 -0.3 0.21 -0.51 t0.49 -0.21 l8.8 0 z M61.78399999999999 5 q0.28 0 0.49 0.21 t0.21 0.49 l0 1.46 q0 0.3 -0.21 0.51 t-0.49 0.21 l-9.7 0 q-0.28 0 -0.49 -0.21 t-0.21 -0.51 l0 -1.46 q0 -0.28 0.21 -0.49 t0.49 -0.21 l9.7 0 z M79.512 18.86 q0.14 0.32 -0.07 0.65 t-0.57 0.33 l-13.32 0 q-0.18 0 -0.34 -0.09 t-0.24 -0.23 q-0.22 -0.32 -0.06 -0.66 l0.62 -1.46 q0.08 -0.2 0.26 -0.32 t0.38 -0.12 l9.32 0 l-3.28 -7.84 l-2.68 6.4 q-0.08 0.2 -0.25 0.31 t-0.39 0.11 l-1.68 0 q-0.38 0 -0.6 -0.32 q-0.08 -0.14 -0.1 -0.32 t0.04 -0.34 l4.06 -9.52 q0.08 -0.2 0.25 -0.32 t0.39 -0.12 l1.92 0 q0.22 0 0.39 0.12 t0.25 0.32 z M92 5.52 q1.22 0.46 2.2 1.48 q2.1 2.12 2.1 5.41 t-2.1 5.43 q-0.98 1.02 -2.2 1.48 q-1.26 0.52 -2.58 0.52 l-5.66 0 q-0.28 0 -0.49 -0.21 t-0.21 -0.49 l0 -1.44 q0 -0.3 0.21 -0.51 t0.49 -0.21 l5.6 0 q1.78 0 2.89 -1.3 t1.11 -3.26 t-1.11 -3.26 t-2.89 -1.3 l-5.6 0 q-0.28 0 -0.49 -0.21 t-0.21 -0.51 l0 -1.44 q0 -0.28 0.21 -0.49 t0.49 -0.21 l5.66 0 q1.32 0 2.58 0.52 z"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

@ -0,0 +1,40 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="3200"
height="1355.480324331485" viewBox="0 0 3200 1355.480324331485">
<g transform="scale(10) translate(10, 10)">
<defs id="SvgjsDefs1385">
<linearGradient id="SvgjsLinearGradient1390">
<stop id="SvgjsStop1391" stop-color="#905e26" offset="0"/>
<stop id="SvgjsStop1392" stop-color="#f5ec9b" offset="0.5"/>
<stop id="SvgjsStop1393" stop-color="#905e26" offset="1"/>
</linearGradient>
<linearGradient id="SvgjsLinearGradient1394">
<stop id="SvgjsStop1395" stop-color="#905e26" offset="0"/>
<stop id="SvgjsStop1396" stop-color="#f5ec9b" offset="0.5"/>
<stop id="SvgjsStop1397" stop-color="#905e26" offset="1"/>
</linearGradient>
</defs>
<g id="SvgjsG1386" featureKey="aMgJeN-0"
transform="matrix(1.5610770874511997,0,0,1.5610770874511997,71.94613967240352,-53.841545411371435)"
fill="#fff">
<path xmlns="http://www.w3.org/2000/svg"
d="M29.399,57.112c0.615,0.308,1.077,0.846,1.077,1.383c0,1.039-1.038,1.961-1.999,1.462l-15.223-7.881 c-0.846-0.499-1.576-0.808-1.576-1.884c0-1.115,0.692-1.385,1.576-1.922l15.223-7.881c1.038-0.346,1.999,0.424,1.999,1.462 c0,0.575-0.461,1.114-1.077,1.423l-13.761,6.918L29.399,57.112z"/>
<path xmlns="http://www.w3.org/2000/svg"
d="M29.033,60.209c-0.208,0-0.413-0.052-0.608-0.152l-15.223-7.881c-0.086-0.05-0.165-0.095-0.242-0.141 c-0.748-0.431-1.395-0.804-1.395-1.843c0-1.057,0.594-1.406,1.346-1.849c0.093-0.054,0.187-0.11,0.284-0.169l15.229-7.885 c0.195-0.066,0.377-0.097,0.558-0.097c0.9,0,1.606,0.728,1.606,1.658c0,0.587-0.437,1.172-1.139,1.522l-13.562,6.818l13.562,6.818 c0.691,0.346,1.139,0.93,1.139,1.484C30.588,59.407,29.861,60.209,29.033,60.209z M28.982,40.419c-0.156,0-0.314,0.026-0.47,0.078 L13.306,48.37c-0.091,0.057-0.188,0.113-0.281,0.167c-0.743,0.439-1.234,0.728-1.234,1.655c0,0.91,0.537,1.219,1.281,1.648 c0.079,0.045,0.158,0.09,0.239,0.139l15.217,7.877c0.162,0.084,0.332,0.127,0.504,0.127c0.697,0,1.33-0.709,1.33-1.488 c0-0.465-0.407-0.98-1.014-1.283l-13.962-7.02l13.962-7.02c0.616-0.308,1.014-0.828,1.014-1.321 C30.363,41.048,29.756,40.419,28.982,40.419z"/>
<path xmlns="http://www.w3.org/2000/svg"
d="M46.385,64.416c-0.231,0.691-0.922,1.077-1.614,0.961c-0.769-0.153-1.269-0.885-1.154-1.692 c0-0.076,0.039-0.191,0.077-0.307l9.88-27.831c0.23-0.692,0.922-1.038,1.614-0.923c0.73,0.154,1.269,0.885,1.153,1.652 c0,0.078-0.039,0.193-0.077,0.27L46.385,64.416z"/>
<path xmlns="http://www.w3.org/2000/svg"
d="M45.016,65.511c-0.088,0-0.177-0.008-0.263-0.022c-0.837-0.167-1.371-0.949-1.247-1.819 c-0.001-0.076,0.038-0.193,0.079-0.318l9.883-27.842c0.207-0.618,0.778-1.02,1.457-1.02c0.094,0,0.188,0.009,0.281,0.023 c0.812,0.172,1.369,0.97,1.247,1.781c0.001,0.086-0.047,0.22-0.087,0.302l-9.875,27.856C46.281,65.084,45.688,65.511,45.016,65.511z M54.925,34.715c-0.58,0-1.068,0.34-1.244,0.867l-9.88,27.833c-0.035,0.104-0.07,0.213-0.07,0.27 c-0.108,0.767,0.349,1.439,1.062,1.582c0.071,0.012,0.147,0.018,0.223,0.018c0.575,0,1.083-0.363,1.263-0.904l9.881-27.872 c0.041-0.085,0.07-0.182,0.07-0.231c0.105-0.712-0.373-1.396-1.064-1.543C55.089,34.722,55.007,34.715,54.925,34.715z"/>
<path xmlns="http://www.w3.org/2000/svg"
d="M84.362,50.192L70.6,43.274c-0.614-0.309-1.075-0.848-1.075-1.423c0-1.038,1.037-1.962,1.998-1.462l15.223,7.881 c0.885,0.537,1.576,0.807,1.576,1.922c0,1.076-0.73,1.385-1.576,1.884l-15.223,7.881c-0.961,0.499-1.998-0.423-1.998-1.462 c0-0.537,0.461-1.075,1.075-1.383L84.362,50.192z"/>
<path xmlns="http://www.w3.org/2000/svg"
d="M70.967,60.209c-0.828,0-1.556-0.802-1.556-1.714c0-0.555,0.447-1.139,1.139-1.484l13.562-6.818L70.55,43.374 c-0.702-0.352-1.139-0.936-1.139-1.522c0-0.913,0.728-1.714,1.556-1.714c0.209,0,0.413,0.051,0.608,0.152l15.223,7.881 c0.104,0.062,0.198,0.119,0.29,0.173c0.753,0.442,1.347,0.792,1.347,1.849c0,1.039-0.646,1.412-1.395,1.843 c-0.078,0.046-0.157,0.091-0.237,0.138l-15.228,7.884C71.38,60.157,71.176,60.209,70.967,60.209z M70.967,40.363 c-0.696,0-1.33,0.709-1.33,1.488c0,0.493,0.397,1.013,1.014,1.321l13.962,7.02l-13.962,7.02c-0.606,0.305-1.014,0.82-1.014,1.283 c0,0.779,0.634,1.488,1.33,1.488c0.172,0,0.342-0.043,0.504-0.127l15.223-7.88c0.075-0.046,0.155-0.091,0.233-0.136 c0.744-0.43,1.282-0.738,1.282-1.648c0-0.928-0.491-1.216-1.235-1.655c-0.093-0.054-0.188-0.11-0.287-0.17l-15.216-7.876 C71.309,40.405,71.139,40.363,70.967,40.363z"/>
</g>
<g id="SvgjsG1387" featureKey="8L6ael-0"
transform="matrix(3.168568052463937,0,0,3.168568052463937,-5.1330821487142195,52.17667742743372)"
fill="#fff">
<path d="M10.56 5.52 q1.22 0.46 2.2 1.48 q2.1 2.12 2.1 5.41 t-2.1 5.43 q-0.98 1.02 -2.2 1.48 q-1.26 0.52 -2.58 0.52 l-5.66 0 q-0.28 0 -0.49 -0.21 t-0.21 -0.49 l0 -1.44 q0 -0.3 0.21 -0.51 t0.49 -0.21 l5.6 0 q1.78 0 2.89 -1.3 t1.11 -3.26 t-1.11 -3.26 t-2.89 -1.3 l-5.6 0 q-0.28 0 -0.49 -0.21 t-0.21 -0.51 l0 -1.44 q0 -0.28 0.21 -0.49 t0.49 -0.21 l5.66 0 q1.32 0 2.58 0.52 z M32.208 18.86 q0.14 0.32 -0.07 0.65 t-0.57 0.33 l-13.32 0 q-0.18 0 -0.34 -0.09 t-0.24 -0.23 q-0.22 -0.32 -0.06 -0.66 l0.62 -1.46 q0.08 -0.2 0.26 -0.32 t0.38 -0.12 l9.32 0 l-3.28 -7.84 l-2.68 6.4 q-0.08 0.2 -0.25 0.31 t-0.39 0.11 l-1.68 0 q-0.38 0 -0.6 -0.32 q-0.08 -0.14 -0.1 -0.32 t0.04 -0.34 l4.06 -9.52 q0.08 -0.2 0.25 -0.32 t0.39 -0.12 l1.92 0 q0.22 0 0.39 0.12 t0.25 0.32 z M45.535999999999994 12.42 q0.86 0.54 1.31 1.32 t0.45 1.74 q0 2.26 -1.72 3.46 q-1.16 0.84 -3.14 1.06 l-0.08 0 q-0.28 0 -0.46 -0.18 q-0.24 -0.22 -0.24 -0.52 l0 -1.44 q0 -0.26 0.18 -0.46 t0.44 -0.24 q0.94 -0.1 1.46 -0.44 q0.4 -0.24 0.54 -0.62 q0.08 -0.22 0.08 -0.56 q0 -0.22 -0.08 -0.38 t-0.28 -0.3 q-0.58 -0.4 -1.5 -0.68 l-0.32 -0.1 q-1 -0.28 -1.84 -0.46 q-0.18 -0.04 -0.58 -0.14 l-0.22 -0.06 q-0.72 -0.18 -1.52 -0.5 q-1.18 -0.5 -1.94 -1.26 q-0.88 -0.88 -0.88 -2.32 q0 -1.98 1.52 -3.22 q1.04 -0.88 2.92 -1.12 q0.32 -0.04 0.55 0.18 t0.23 0.52 l0 1.44 q0 0.26 -0.16 0.46 t-0.41 0.23 t-0.51 0.11 q-0.58 0.22 -0.82 0.43 t-0.34 0.43 q-0.1 0.34 -0.1 0.64 q0 0.16 0.18 0.34 q0.28 0.28 0.82 0.5 q0.24 0.1 0.84 0.3 l2.5 0.62 l0.12 0.04 q0.94 0.26 1.36 0.4 q0.94 0.32 1.64 0.78 z M42.196 7.9 q-0.24 -0.06 -0.39 -0.25 t-0.15 -0.45 l0 -1.46 q0 -0.32 0.26 -0.54 q0.1 -0.1 0.26 -0.13 t0.3 -0.01 q1.76 0.28 2.86 1.2 q1.5 1.24 1.6 3.16 q0.02 0.28 -0.19 0.51 t-0.51 0.23 l-1.56 0 q-0.26 0 -0.46 -0.18 t-0.22 -0.44 q-0.08 -0.52 -0.34 -0.86 q-0.42 -0.5 -1.3 -0.76 q-0.06 0 -0.08 -0.02 l-0.08 0 z M39.855999999999995 17.08 q0.24 0.04 0.4 0.24 t0.16 0.44 l0 1.48 q0 0.32 -0.24 0.54 q-0.2 0.16 -0.46 0.16 l-0.1 0 q-1.86 -0.28 -3.06 -1.22 q-1.56 -1.24 -1.76 -3.46 q-0.04 -0.32 0.18 -0.55 t0.52 -0.23 l1.58 0 q0.28 0 0.48 0.19 t0.22 0.47 q0.06 1 0.98 1.54 q0.42 0.24 1.1 0.4 z M60.88399999999999 11.12 q0.3 0 0.51 0.21 t0.21 0.51 l0 1.48 q0 0.28 -0.21 0.49 t-0.51 0.21 l-6.58 0 l0 5.12 q0 0.28 -0.2 0.49 t-0.5 0.21 l-1.52 0 q-0.28 0 -0.49 -0.21 t-0.21 -0.49 l0 -7.3 q0 -0.3 0.21 -0.51 t0.49 -0.21 l8.8 0 z M61.78399999999999 5 q0.28 0 0.49 0.21 t0.21 0.49 l0 1.46 q0 0.3 -0.21 0.51 t-0.49 0.21 l-9.7 0 q-0.28 0 -0.49 -0.21 t-0.21 -0.51 l0 -1.46 q0 -0.28 0.21 -0.49 t0.49 -0.21 l9.7 0 z M79.512 18.86 q0.14 0.32 -0.07 0.65 t-0.57 0.33 l-13.32 0 q-0.18 0 -0.34 -0.09 t-0.24 -0.23 q-0.22 -0.32 -0.06 -0.66 l0.62 -1.46 q0.08 -0.2 0.26 -0.32 t0.38 -0.12 l9.32 0 l-3.28 -7.84 l-2.68 6.4 q-0.08 0.2 -0.25 0.31 t-0.39 0.11 l-1.68 0 q-0.38 0 -0.6 -0.32 q-0.08 -0.14 -0.1 -0.32 t0.04 -0.34 l4.06 -9.52 q0.08 -0.2 0.25 -0.32 t0.39 -0.12 l1.92 0 q0.22 0 0.39 0.12 t0.25 0.32 z M92 5.52 q1.22 0.46 2.2 1.48 q2.1 2.12 2.1 5.41 t-2.1 5.43 q-0.98 1.02 -2.2 1.48 q-1.26 0.52 -2.58 0.52 l-5.66 0 q-0.28 0 -0.49 -0.21 t-0.21 -0.49 l0 -1.44 q0 -0.3 0.21 -0.51 t0.49 -0.21 l5.6 0 q1.78 0 2.89 -1.3 t1.11 -3.26 t-1.11 -3.26 t-2.89 -1.3 l-5.6 0 q-0.28 0 -0.49 -0.21 t-0.21 -0.51 l0 -1.44 q0 -0.28 0.21 -0.49 t0.49 -0.21 l5.66 0 q1.32 0 2.58 0.52 z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 679 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 896 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

43
public/assets/ddd.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 669 B

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 15 KiB

BIN
public/assets/grid.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
public/assets/grid2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 867 KiB

BIN
public/assets/grid3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 661 KiB

BIN
public/assets/grid4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

BIN
public/assets/grid6.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

BIN
public/assets/iPhone.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g data-name="Layer 2">
<g data-name="video">
<rect width="24" height="24" opacity="0"/>
<path d="M21 7.15a1.7 1.7 0 0 0-1.85.3l-2.15 2V8a3 3 0 0 0-3-3H5a3 3 0 0 0-3 3v8a3 3 0 0 0 3 3h9a3 3 0 0 0 3-3v-1.45l2.16 2a1.74 1.74 0 0 0 1.16.45 1.68 1.68 0 0 0 .69-.15 1.6 1.6 0 0 0 1-1.48V8.63A1.6 1.6 0 0 0 21 7.15z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 438 B

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,300 @@
# Object Export From Tinkercad Server 2015
mtllib obj.mtl
o obj_0
v 4 3.5 4
v 4 3.568 3.482
v 4 3.768 3
v 4 4.086 2.586
v 4 4.5 2.268
v 4 4.982 2.068
v 4 5.5 2
v 4 6.018 2.068
v 4 6.5 2.268
v 4 6.914 2.586
v 4 7.232 3
v 4 7.432 3.482
v 4 7.5 4
v 4 7.432 4.518
v 4 7.232 5
v 4 6.914 5.414
v 4 6.5 5.732
v 4 6.018 5.932
v 4 5.5 6
v 4 4.982 5.932
v 4 4.5 5.732
v 4 4.086 5.414
v 4 3.768 5
v 4 3.568 4.518
v -8.308 4.5 7.952
v 1 4.7272 4.2072
v -8.587 4.5 7.81
v 1 4.7 4
v -8.81 4.5 7.587
v 1 4.8072 4.4
v -8.952 4.5 7.308
v 1 4.9344 4.5656
v -9 4.5 7.0043
v 0.952 6.5 7.308
v 1 5.1 4.6928
v 1 6.5 7.0043
v 0.81 6.5 7.587
v 1 5.2928 4.7728
v 0.587 6.5 7.81
v 0.308 6.5 7.952
v 1 5.5 4.8
v 0.0033 6.5 8
v 1 5.7072 4.7728
v 1 5.9 4.6928
v 1 6.0656 4.5656
v 1 6.1928 4.4
v 1 4.5 7.0043
v 0.952 4.5 7.308
v 0.81 4.5 7.587
v 0.587 4.5 7.81
v 0.308 4.5 7.952
v 1 6.2728 4.2072
v 0.0033 4.5 8
v 1 6.3 4
v -8.0043 4.5 0
v -8.952 4.5 0.692
v -9 4.5 0.9967
v 1 6.2728 3.7928
v -8.81 4.5 0.413
v -8.587 4.5 0.19
v 1 6.1928 3.6
v -8.308 4.5 0.048
v 1 6.0656 3.4344
v 1 5.9 3.3072
v -8.0043 6.5 8
v -8.308 6.5 7.952
v 1 5.7072 3.2272
v -8.587 6.5 7.81
v 1 5.5 3.2
v -8.81 6.5 7.587
v -8.952 6.5 7.308
v 1 5.2928 3.2272
v -9 6.5 7.0043
v 1 5.1 3.3072
v 1 4.9344 3.4344
v 1 4.8072 3.6
v 1 4.7272 3.7928
v -9 6.5 0.9967
v -8.0043 4.5 8
v 0.81 6.5 0.413
v 0.587 6.5 0.19
v 0.952 6.5 0.692
v 1 6.5 0.9967
v 0.0033 6.5 0
v 0.0033 4.5 0
v 0.308 4.5 0.048
v -8.0043 6.5 0
v 0.587 4.5 0.19
v 0.81 4.5 0.413
v 0.952 4.5 0.692
v 1 4.5 0.9967
v -8.952 6.5 0.692
v -8.81 6.5 0.413
v -8.587 6.5 0.19
v -8.308 6.5 0.048
v 0.308 6.5 0.048
# 96 vertices
g group_0_8273816
usemtl color_8273816
s 0
f 1 2 3
f 1 3 4
f 1 4 5
f 1 5 6
f 1 6 7
f 1 7 8
f 1 8 9
f 1 9 10
f 1 10 11
f 1 11 12
f 1 12 13
f 1 13 14
f 1 14 15
f 1 15 16
f 1 16 17
f 1 17 18
f 1 18 19
f 1 19 20
f 1 20 21
f 1 21 22
f 1 22 23
f 1 23 24
f 28 1 26
f 24 26 1
f 23 30 26
f 23 26 24
f 22 32 30
f 22 30 23
f 32 22 35
f 21 35 22
f 49 48 37
f 35 21 38
f 20 38 21
f 19 41 38
f 19 38 20
f 41 19 43
f 18 43 19
f 34 48 36
f 17 44 43
f 17 43 18
f 34 37 48
f 16 45 44
f 16 44 17
f 49 37 39
f 45 16 46
f 15 46 16
f 53 40 42
f 36 48 47
f 49 39 50
f 51 50 40
f 39 40 50
f 46 15 52
f 14 52 15
f 40 53 51
f 52 54 36
f 83 36 54
f 54 58 83
f 58 61 83
f 61 63 83
f 93 60 59
f 64 83 63
f 69 72 91
f 72 74 91
f 73 33 71
f 74 75 91
f 75 76 91
f 25 65 66
f 66 68 27
f 66 27 25
f 29 27 70
f 68 70 27
f 91 76 77
f 91 77 28
f 70 71 31
f 70 31 29
f 71 33 31
f 65 25 79
f 57 33 78
f 73 78 33
f 42 65 53
f 53 65 79
f 81 88 86
f 80 89 88
f 80 88 81
f 89 80 90
f 82 90 80
f 82 83 90
f 91 90 83
f 84 86 85
f 55 87 85
f 84 85 87
f 95 87 55
f 86 91 85
f 88 91 86
f 89 90 88
f 91 88 90
f 92 56 78
f 93 56 92
f 94 60 93
f 94 95 60
f 62 60 95
f 56 55 57
f 91 57 85
f 62 95 55
f 55 85 57
f 59 55 56
f 60 62 59
f 55 59 62
f 78 56 57
f 93 59 56
f 29 31 27
f 33 27 31
f 27 33 25
f 13 54 52
f 13 52 14
f 54 13 58
f 12 58 13
f 49 50 57
f 51 57 50
f 53 57 51
f 79 33 53
f 61 58 11
f 12 11 58
f 63 61 10
f 11 10 61
f 10 9 64
f 10 64 63
f 49 57 48
f 91 47 57
f 25 33 79
f 57 53 33
f 47 48 57
f 9 8 67
f 9 67 64
f 96 73 81
f 84 73 96
f 69 67 7
f 8 7 67
f 80 81 73
f 7 6 72
f 7 72 69
f 74 72 5
f 6 5 72
f 93 92 94
f 78 94 92
f 95 78 87
f 65 42 83
f 75 74 4
f 5 4 74
f 66 83 68
f 70 68 83
f 4 3 76
f 4 76 75
f 39 37 40
f 42 40 37
f 42 37 34
f 3 2 77
f 3 77 76
f 34 36 42
f 28 77 1
f 2 1 77
f 36 83 42
f 65 83 66
f 71 70 83
f 73 71 83
f 82 80 73
f 83 82 73
f 87 78 84
f 94 78 95
f 73 84 78
f 96 86 84
f 81 86 96
f 67 69 83
f 64 67 83
f 69 91 83
f 91 28 47
f 28 26 47
f 26 30 47
f 30 32 47
f 38 41 47
f 32 35 47
f 35 38 47
f 41 36 47
f 41 43 36
f 43 44 36
f 44 45 36
f 45 46 36
f 46 52 36
# 188 faces
#end of obj_0

View File

@ -0,0 +1,10 @@
{
"objects": [
{
"name": "Server",
"id": "server",
"type": "glb",
"path": "models/server_racking_system.glb"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 821 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 688 KiB

3
public/b2b2c.csv Normal file
View File

@ -0,0 +1,3 @@
"ID","NAME","POSITION-X","POSITION-Y","POSITION-Z", "ROTATION-X","ROTATION-Y","ROTATION-Z", "SCALE-X","SCALE-Y","SCALE-Z"
"id2533e7b6-118a-46d0-bad9-11e73462798b","Commerce",1,0,0,0,0,0,.1,.1,.1,
,Platform,1.3,0,0,0,0,0,.1,.1,.1,
Can't render this file because it has a wrong number of fields in line 2.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

View File

@ -1,16 +1,17 @@
{ {
"name": "Deep Diagram", "name": "Deep Diagram",
"display": "standalone", "display": "standalone",
"start_url": "https://deepdiagram.com", "start_url": "https://www.deepdiagram.com",
"scope": "https://deepdiagram.com", "id": "com.deepdiagram",
"scope": "https://www.deepdiagram.com",
"short_name": "Deep Diagram", "short_name": "Deep Diagram",
"theme_color": "#000000", "theme_color": "#000000",
"background_color": "#000000", "background_color": "#000000",
"description": "Immersive diagraming tool to dig into deeper meaning behind your ideas", "description": "An immersive mind mapping and diagramming tool that lets you create, share, and visualize data on the Oculus Quest and desktop browsers.",
"icons": [ "icons": [
{ {
"src": "/assets/android-icon-192x192.png", "src": "/assets/Android.png",
"sizes": "192x192", "sizes": "196x196",
"type": "image/png", "type": "image/png",
"purpose": "any" "purpose": "any"
}, },
@ -28,22 +29,22 @@
], ],
"screenshots": [ "screenshots": [
{ {
"src": "/assets/com.oculus.browser-20230121-110512.jpg", "src": "/assets/screenshot1.png",
"sizes": "1024x1024", "sizes": "1024x1024",
"type": "image/jpeg", "type": "image/png",
"label": "Example 1" "label": "Example of a few boxes on the web"
}, },
{ {
"src": "/assets/com.oculus.browser-20230121-110746.jpg", "src": "/assets/screenshot2.png",
"sizes": "1024x1024", "sizes": "1024x1024",
"type": "image/jpeg", "type": "image/png",
"label": "Example 2" "label": "Example of menus on the web"
}, },
{ {
"src": "/assets/com.oculus.browser-20230121-111323.jpg", "src": "/assets/screenshot3.png",
"sizes": "1024x1024", "sizes": "1024x1024",
"type": "image/jpeg", "type": "image/png",
"label": "Complex Architecture Diagram" "label": "Example showing the web app on the Oculus Quest"
} }
] ]
} }

141
public/pages/privacy.html Normal file
View File

@ -0,0 +1,141 @@
<html lang="">
<head>
<title>Deep Diagram Privacy Policy</title>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1" name="viewport"/>
<meta content="width=device-width, initial-scale=1, height=device-height" name="viewport">
<link href="/styles.css" rel="stylesheet">
<link href="/assets/favicon-32x32.png" rel="icon" sizes="32x32" type="image/png">
<link href="/assets/favicon-16x16.png" rel="icon" sizes="16x16" type="image/png">
<link href="/assets/favicon-96x96.png" rel="icon" sizes="96x96" type="image/png">
<link as="script" href="/newRelic.js" rel="preload">
<script src="/newRelic.js"></script>
<style>
body * {
color: #FFFFFF;
}
div#privacyPolicy {
z-index: 1;
position: absolute;
width: 80%;
height: 80%;
top: 9%;
left: 9%;
overflow-y: scroll;
background-color: rgba(0, 0, .2, 0.6);
padding: 40px;
border-radius: 30px;
border-color: #FFD700;
border-style: solid;
}
h1 {
color: #FFf0d0;
text-align: left;
}
li {
font-size: smaller;
}
</style>
</head>
<body>
<img alt="background grid" id="loadingGrid" src="/assets/grid3.jpg"/>
<div id="privacyPolicy">
<p>
This privacy policy ("policy") will help you understand how Immersive Idea LLC. ("us", "we",
"our") uses and protects the data you provide to us when you visit and use https://www.deepdiagram.com
website.
</p>
We reserve the right to change this policy at any given time, of which you will be
promptly updated. If you want to make sure that you are up to date with the latest
changes, we advise you to frequently visit this page.
<p>
<h1>What User Data We Collect</h1>
<p>
When you visit the website, we may collect the following data:
<ul>
<li>Your IP address.</li>
<li>Your contact information and email address.</li>
<li>Other information such as interests and preferences.</li>
<li>Data profile regarding your online behavior on our website.</li>
</ul>
<h1>Why We Collect Your Data</h1>
<p>
We are collecting your data for several reasons:
</p>
<ul>
<li>To better understand your needs.</li>
<li>To improve our services and products.</li>
<li>To send you promotional emails containing the information we think you will find interesting.</li>
<li>To contact you to fill out surveys and participate in other types of market
research.
</li>
<li>To customize our website according to your online behavior and personal
preferences.
</li>
</ul>
<h1>
Safeguarding and Securing the Data
</h1>
<p>Immersive Idea LLC is committed to securing your data and keeping it confidential.</p>
<p>Immersive Idea LLC has done all in its power to prevent data theft, unauthorized access,
and disclosure by implementing the latest technologies and software, which help us
safeguard all the information we collect online.
</p>
<h1>Our Cookie Policy</h1>
<p>
Once you agree to allow our website to use cookies, you also agree to use the data it
collects regarding your online behavior (analyze web traffic, web pages you visit and
spend the most time on).
</p>
<p>
The data we collect by using cookies is used to customize our website to your needs.
After we use the data for statistical analysis, the data is completely removed from our
systems.
</p>
<p>
Please note that cookies don't allow us to gain control of your computer in any way.
They are strictly used to monitor which pages you find useful and which you do not so
that we can provide a better experience for you.
</p>
<p>
If you want to disable cookies, you can do it by accessing the settings of your internet
browser. You can visit https://www.internetcookies.com, which contains comprehensive
information on how to do this on a wide variety of browsers and devices.
</p>
<h1>Links to Other Websites</h1>
<p>
Our website may contain links that lead to other websites. If you click on these links
Immersive Idea LLC is not held responsible for your data and privacy protection. Visiting
those websites is not governed by this privacy policy agreement. Make sure to read the
privacy policy documentation of the website you go to from our website.
</p>
<h1>
Restricting the Collection of your Personal Data
</h1>
<p>
At some point, you might wish to restrict the use and collection of your personal data.
You can achieve this by doing the following:</p>
<p>
When you are filling the forms on the website, make sure to check if there is a box
which you can leave unchecked, if you don't want to disclose your personal information.
If you have already agreed to share your information with us, feel free to contact us via
email (support@immersiveidea.com) and we will be more than happy to change this for you.</p>
<p>
Immersive Idea LLC will not lease, sell or distribute your personal information to any third
parties, unless we have your permission. We might do so if the law forces us. Your
personal information will be used when we need to send you promotional materials if
you agree to this privacy policy.
</p>
<a href="https://www.deepdiagram.com">Home</a>
</div>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 630 KiB

35
public/styles.css Normal file
View File

@ -0,0 +1,35 @@
body {
width: 100vw;
height: 100vh;
margin: 0;
padding: 0;
background-color: #000;
background-image: url("/assets/grid6.jpg");
aspect-ratio: auto;
font-family: Roboto, sans-serif;
font-size: large;
color: #4444ee;
}
.scene {
width: 100vw;
height: 100vh;
overflow: hidden;
}
#gameCanvas {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
background: transparent;
}
#loadingGrid {
position: relative;
z-index: -1;
width: 100%;
height: 100%;
}

View File

@ -1,12 +1,25 @@
const VERSION = '0'; importScripts('https://storage.googleapis.com/workbox-cdn/releases/7.1.0/workbox-sw.js');
const CACHE = "pwabuilder-offline"; const VERSION = '0.0.8-19';
const PRECACHE_ASSETS = [ const CACHE = "deepdiagram";
'/grass1.jpeg', const IMAGEDELIVERY_CACHE = "deepdiagram-images";
'/loading-loading-forever.gif', const MAPTILE_CACHE = 'maptiler';
'/outdoor_field2.jpeg'
]
importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-sw.js');
// TODO: replace the following with the correct offline fallback page i.e.: const offlineFallbackPage = "offline.html";
const offlineFallbackPage = "/";
/*self.addEventListener('install', async (event) => {
self.skipWaiting();
});
self.addEventListener('activate', async (event) => {
self.skipWaiting();
self.clients.matchAll({
type: 'window'
}).then(windowClients => {
windowClients.forEach((windowClient) => {
windowClient.navigate(windowClient.url);
});
});
});
*/
self.addEventListener("message", (event) => { self.addEventListener("message", (event) => {
if (event.data && event.data.type === "SKIP_WAITING") { if (event.data && event.data.type === "SKIP_WAITING") {
self.skipWaiting(); self.skipWaiting();
@ -14,33 +27,74 @@ self.addEventListener("message", (event) => {
}); });
/*self.addEventListener('install', async (event) => {
event.waitUntil(
caches.open(CACHE)
.then((cache) => cache.add(offlineFallbackPage))
);
});
*/
/*if (workbox.navigationPreload.isSupported()) {
workbox.navigationPreload.enable();
}*/
workbox.routing.registerRoute( workbox.routing.registerRoute(
new RegExp('/.*\\.png'), new RegExp('/.*\\.wasm'),
new workbox.strategies.StaleWhileRevalidate({ new workbox.strategies.StaleWhileRevalidate({
cacheName: CACHE cacheName: CACHE
}) })
); );
workbox.routing.registerRoute( workbox.routing.registerRoute(
new RegExp('/.*\\.jpeg'), new RegExp('.*api.maptiler.com/.*'),
new workbox.strategies.CacheFirst({
plugins: [
new workbox.expiration.ExpirationPlugin({
maxEntries: 256,
maxAgeSeconds: 60 * 60 * 24 * 30,
purgeOnQuotaError: true,
matchOptions: {
ignoreVary: true
}
}),
new workbox.cacheableResponse.CacheableResponsePlugin({
statuses: [0, 200]
})
],
cacheName: MAPTILE_CACHE
})
);
/*workbox.routing.registerRoute(
new RegExp('/assets/.*'),
new workbox.strategies.StaleWhileRevalidate({ new workbox.strategies.StaleWhileRevalidate({
cacheName: CACHE cacheName: CACHE
}) })
); );
workbox.routing.registerRoute( */
new RegExp('/.*\\.jpg'),
/*workbox.routing.registerRoute(
new RegExp('/db/.*'),
new workbox.strategies.StaleWhileRevalidate({ new workbox.strategies.StaleWhileRevalidate({
cacheName: CACHE cacheName: CACHE
}) })
); );
*/
workbox.routing.registerRoute( workbox.routing.registerRoute(
new RegExp('/.*\\.glb'), new RegExp('/.*\\.glb'),
new workbox.strategies.StaleWhileRevalidate({ new workbox.strategies.StaleWhileRevalidate({
cacheName: CACHE cacheName: CACHE
}) })
); );
/*
workbox.routing.registerRoute( workbox.routing.registerRoute(
new RegExp('/login'), new RegExp('/.*\\.css'),
new workbox.strategies.NetworkFirst() new workbox.strategies.StaleWhileRevalidate({
) cacheName: CACHE
})
);
*/

245
public/templates/demo.json Normal file
View File

@ -0,0 +1,245 @@
{
"name": "demo",
"dbName": "demo",
"exportDate": "2025-11-20T14:32:58.031Z",
"version": "1.0",
"entities": [
{
"id": "id0c8fd8ad-7dc8-41fa-b61f-b22c2d2b3eb8",
"position": {
"x": 0.4000000059604645,
"y": 1,
"z": 2.9000000953674316
},
"rotation": {
"x": 0,
"y": 3.141592653589793,
"z": 0
},
"last_seen": "2025-11-20T14:30:54.865Z",
"template": "#cylinder-template",
"scale": {
"x": 0.1,
"y": 0.1,
"z": 0.1
},
"color": "#FF00FF",
"text": "db",
"_id": "id0c8fd8ad-7dc8-41fa-b61f-b22c2d2b3eb8"
},
{
"from": "ide476ec05-9aac-42c9-87f1-ba7f18141767",
"to": "idb75dff6c-e056-4a15-b179-adfb2bec793a",
"type": "entity",
"template": "#connection-template",
"color": "#000000",
"id": "id18bf9938-a0b7-4e65-bf91-38697064698a",
"_id": "id18bf9938-a0b7-4e65-bf91-38697064698a"
},
{
"from": "id5c0dab44-2ef8-406e-b0ca-3aeea5b820b9",
"to": "idb75dff6c-e056-4a15-b179-adfb2bec793a",
"type": "entity",
"template": "#connection-template",
"color": "#000000",
"id": "id47bf19b7-6263-44fa-b289-1f9f63aa6aff",
"_id": "id47bf19b7-6263-44fa-b289-1f9f63aa6aff"
},
{
"from": "id5c0dab44-2ef8-406e-b0ca-3aeea5b820b9",
"to": "idac543949-c285-4f4b-ab07-d6fd2bbf7bb5",
"type": "entity",
"template": "#connection-template",
"color": "#000000",
"id": "id50594820-ace6-44c7-be5c-2b0747549c75",
"_id": "id50594820-ace6-44c7-be5c-2b0747549c75"
},
{
"from": "id5c0dab44-2ef8-406e-b0ca-3aeea5b820b9",
"to": "id5c0dab44-2ef8-406e-b0ca-3aeea5b820b9",
"type": "entity",
"template": "#connection-template",
"color": "#000000",
"id": "id562bf787-0d11-413c-bc2d-194bf05275fd",
"_id": "id562bf787-0d11-413c-bc2d-194bf05275fd"
},
{
"from": "idb75dff6c-e056-4a15-b179-adfb2bec793a",
"to": "id0c8fd8ad-7dc8-41fa-b61f-b22c2d2b3eb8",
"type": "entity",
"template": "#connection-template",
"color": "#000000",
"id": "id5b622c06-95f1-4d04-b023-b1a6851d2107",
"_id": "id5b622c06-95f1-4d04-b023-b1a6851d2107"
},
{
"id": "id5c0dab44-2ef8-406e-b0ca-3aeea5b820b9",
"position": {
"x": 0.4000000059604645,
"y": 1.7000000476837158,
"z": 2.9000000953674316
},
"rotation": {
"x": 0,
"y": 3.141592653589793,
"z": 0
},
"last_seen": "2025-11-20T14:28:52.061Z",
"template": "#sphere-template",
"text": "browser",
"scale": {
"x": 0.1,
"y": 0.1,
"z": 0.1
},
"color": "#8B4513",
"_id": "id5c0dab44-2ef8-406e-b0ca-3aeea5b820b9"
},
{
"from": "idac543949-c285-4f4b-ab07-d6fd2bbf7bb5",
"to": "id5c0dab44-2ef8-406e-b0ca-3aeea5b820b9",
"type": "entity",
"template": "#connection-template",
"color": "#000000",
"id": "id6f4208a8-9b17-45a8-b030-83a80d7e09bf",
"_id": "id6f4208a8-9b17-45a8-b030-83a80d7e09bf"
},
{
"from": "idf1cf90c7-cc2f-4ccc-9bd6-274752f5b66f",
"to": "id5c0dab44-2ef8-406e-b0ca-3aeea5b820b9",
"type": "entity",
"template": "#connection-template",
"color": "#000000",
"id": "id7205d022-db34-4705-8e3b-930ae0351376",
"_id": "id7205d022-db34-4705-8e3b-930ae0351376"
},
{
"from": "idb75dff6c-e056-4a15-b179-adfb2bec793a",
"to": "ide476ec05-9aac-42c9-87f1-ba7f18141767",
"type": "entity",
"template": "#connection-template",
"color": "#000000",
"id": "id74b9b638-8148-4d98-a715-981f7aebd3bb",
"_id": "id74b9b638-8148-4d98-a715-981f7aebd3bb"
},
{
"from": "id0c8fd8ad-7dc8-41fa-b61f-b22c2d2b3eb8",
"to": "idb75dff6c-e056-4a15-b179-adfb2bec793a",
"type": "entity",
"template": "#connection-template",
"color": "#000000",
"id": "id7af34b2d-f790-45c9-9fce-6c627de1410e",
"_id": "id7af34b2d-f790-45c9-9fce-6c627de1410e"
},
{
"from": "id5c0dab44-2ef8-406e-b0ca-3aeea5b820b9",
"to": "idf1cf90c7-cc2f-4ccc-9bd6-274752f5b66f",
"type": "entity",
"template": "#connection-template",
"color": "#000000",
"id": "id8bdd38de-6ac9-44c1-95e9-015ed85c0b7a",
"_id": "id8bdd38de-6ac9-44c1-95e9-015ed85c0b7a"
},
{
"id": "idac543949-c285-4f4b-ab07-d6fd2bbf7bb5",
"position": {
"x": -0.6000000238418579,
"y": 1.7000000476837158,
"z": 2.799999952316284
},
"rotation": {
"x": -2.4492937051703357e-16,
"y": 3.141592653589793,
"z": -2.4492937051703357e-16
},
"last_seen": "2025-11-20T14:32:19.261Z",
"template": "#box-template",
"text": "api",
"scale": {
"x": 0.1,
"y": 0.1,
"z": 0.1
},
"color": "#0000FF",
"_id": "idac543949-c285-4f4b-ab07-d6fd2bbf7bb5"
},
{
"id": "idb75dff6c-e056-4a15-b179-adfb2bec793a",
"position": {
"x": 0.4000000059604645,
"y": 1.2999999523162842,
"z": 2.9000000953674316
},
"rotation": {
"x": 0,
"y": 3.141592653589793,
"z": 0
},
"last_seen": "2025-11-20T14:28:36.027Z",
"template": "#box-template",
"text": "server",
"scale": {
"x": 0.1,
"y": 0.1,
"z": 0.1
},
"color": "#006400",
"_id": "idb75dff6c-e056-4a15-b179-adfb2bec793a"
},
{
"from": "idb75dff6c-e056-4a15-b179-adfb2bec793a",
"to": "id5c0dab44-2ef8-406e-b0ca-3aeea5b820b9",
"type": "entity",
"template": "#connection-template",
"color": "#000000",
"id": "idd6098a95-f534-4126-9aad-9948fdc724c6",
"_id": "idd6098a95-f534-4126-9aad-9948fdc724c6"
},
{
"id": "ide476ec05-9aac-42c9-87f1-ba7f18141767",
"position": {
"x": -0.6000000238418579,
"y": 1.2999999523162842,
"z": 2.799999952316284
},
"rotation": {
"x": -2.4492931757747437e-16,
"y": 3.141592653589793,
"z": -6.429647808784774e-40
},
"last_seen": "2025-11-20T14:30:59.486Z",
"template": "#box-template",
"scale": {
"x": 0.1,
"y": 0.1,
"z": 0.1
},
"color": "#0000FF",
"text": "api",
"_id": "ide476ec05-9aac-42c9-87f1-ba7f18141767"
},
{
"id": "idf1cf90c7-cc2f-4ccc-9bd6-274752f5b66f",
"position": {
"x": 0.4000000059604645,
"y": 2.200000047683716,
"z": 3
},
"rotation": {
"x": -2.4492937051703357e-16,
"y": 3.141592653589793,
"z": -2.4492937051703357e-16
},
"last_seen": "2025-11-20T14:28:58.876Z",
"template": "#person-template",
"text": "user",
"scale": {
"x": 0.1,
"y": 0.1,
"z": 0.1
},
"color": "#FFE4B5",
"_id": "idf1cf90c7-cc2f-4ccc-9bd6-274752f5b66f"
}
]
}

Binary file not shown.

124
server.js
View File

@ -1,15 +1,133 @@
import express from "express"; import express from "express";
import ViteExpress from "vite-express"; import ViteExpress from "vite-express";
import cors from "cors";
import dotenv from "dotenv"; import dotenv from "dotenv";
import expressProxy from "express-http-proxy"; import newrelic from "newrelic";
import apiRoutes from "./server/api/index.js";
import { pouchApp, PouchDB } from "./server/services/databaseService.js";
import { dbAuthMiddleware } from "./server/middleware/dbAuth.js";
// Load .env.local first, then fall back to .env
dotenv.config({ path: '.env.local' });
dotenv.config(); dotenv.config();
// Console shim to forward logs to New Relic while preserving local output
const originalConsole = {
log: console.log.bind(console),
error: console.error.bind(console),
warn: console.warn.bind(console),
info: console.info.bind(console)
};
function forwardToNewRelic(level, args) {
const message = args.map(arg =>
typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
).join(' ');
newrelic.recordLogEvent({
message,
level,
timestamp: Date.now()
});
}
console.log = (...args) => {
forwardToNewRelic('info', args);
originalConsole.log(...args);
};
console.error = (...args) => {
forwardToNewRelic('error', args);
originalConsole.error(...args);
};
console.warn = (...args) => {
forwardToNewRelic('warn', args);
originalConsole.warn(...args);
};
console.info = (...args) => {
forwardToNewRelic('info', args);
originalConsole.info(...args);
};
const app = express(); const app = express();
app.use("/api", expressProxy("local.immersiveidea.com")); // CORS configuration for split deployment
// In combined mode, same-origin requests don't need CORS
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(",") || [];
if (allowedOrigins.length > 0) {
app.use(cors({
origin: allowedOrigins,
credentials: true,
}));
}
// Parse JSON for all routes EXCEPT /pouchdb (express-pouchdb handles its own body parsing)
app.use((req, res, next) => {
if (req.path.startsWith('/pouchdb')) {
return next();
}
express.json()(req, res, next);
});
// API routes
app.use("/api", apiRoutes);
ViteExpress.listen(app, process.env.PORT || 3001, () => console.log("Server is listening...")); // Test endpoint to verify PouchDB is working
app.get("/pouchdb-test/:dbname", async (req, res) => {
try {
const dbName = req.params.dbname;
console.log(`[Test] Creating database: ${dbName}`);
const db = new PouchDB(dbName);
const info = await db.info();
console.log(`[Test] Database info:`, info);
// Try to add a test doc
const result = await db.put({ _id: 'test-doc', hello: 'world' });
console.log(`[Test] Added doc:`, result);
// Read it back
const doc = await db.get('test-doc');
console.log(`[Test] Got doc:`, doc);
res.json({ success: true, info, doc });
} catch (err) {
console.error(`[Test] Error:`, err);
res.status(500).json({ error: err.message, stack: err.stack });
}
});
// PouchDB database sync endpoint with auth middleware
// Public databases (/pouchdb/public-*) are accessible without auth
// Private databases (/pouchdb/private-*) require authentication
// Patch req.query for Express 5 compatibility with express-pouchdb
app.use("/pouchdb", dbAuthMiddleware, (req, res, next) => {
// Express 5 makes req.query read-only, but express-pouchdb needs to write to it
// Redefine as writable property
Object.defineProperty(req, 'query', {
value: { ...req.query },
writable: true,
configurable: true
});
next();
}, pouchApp, (err, req, res, next) => {
console.error('[PouchDB Error]', err);
res.status(500).json({ error: err.message, stack: err.stack });
});
// Check if running in API-only mode (split deployment)
const apiOnly = process.env.API_ONLY === "true";
if (apiOnly) {
// API-only mode: no static file serving
app.listen(process.env.PORT || 3000, () => {
console.log(`API server running on port ${process.env.PORT || 3000}`);
});
} else {
// Combined mode: Vite handles static files + SPA
ViteExpress.listen(app, process.env.PORT || 3001, () => {
console.log(`Server running on port ${process.env.PORT || 3001}`);
});
}

178
server/api/claude.js Normal file
View File

@ -0,0 +1,178 @@
import { Router } from "express";
import { getSession, addMessage, getConversationForAPI } from "../services/sessionStore.js";
import { trackUsage, getUsageSummary, formatCost, getSessionUsage } from "../services/usageTracker.js";
const router = Router();
const ANTHROPIC_API_URL = "https://api.anthropic.com";
/**
* Build entity context string for the system prompt
*/
function buildEntityContext(entities) {
if (!entities || entities.length === 0) {
return "\n\nThe diagram is currently empty.";
}
const entityList = entities.map(e => {
const shape = e.template?.replace('#', '').replace('-template', '') || 'unknown';
const pos = e.position || { x: 0, y: 0, z: 0 };
return `- ${e.text || '(no label)'} (${shape}, ${e.color || 'unknown'}) at (${pos.x?.toFixed(1)}, ${pos.y?.toFixed(1)}, ${pos.z?.toFixed(1)})`;
}).join('\n');
return `\n\n## Current Diagram State\nThe diagram currently contains ${entities.length} entities:\n${entityList}`;
}
// Express 5 uses named parameters for wildcards
router.post("/*path", async (req, res) => {
const requestStart = Date.now();
console.log(`[Claude API] ========== REQUEST START ==========`);
const apiKey = process.env.ANTHROPIC_API_KEY;
if (!apiKey) {
console.error(`[Claude API] ERROR: API key not configured`);
return res.status(500).json({ error: "API key not configured" });
}
// Get the path after /api/claude (e.g., /v1/messages)
// Express 5 returns path segments as an array
const pathParam = req.params.path;
const path = "/" + (Array.isArray(pathParam) ? pathParam.join("/") : pathParam || "");
console.log(`[Claude API] Path: ${path}`);
// Check for session-based request
const { sessionId, ...requestBody } = req.body;
let modifiedBody = requestBody;
console.log(`[Claude API] Session ID: ${sessionId || 'none'}`);
console.log(`[Claude API] Model: ${requestBody.model}`);
console.log(`[Claude API] Messages count: ${requestBody.messages?.length || 0}`);
if (sessionId) {
const session = getSession(sessionId);
if (session) {
console.log(`[Claude API] Session found: ${session.entities.length} entities, ${session.conversationHistory.length} messages in history`);
// Inject entity context into system prompt
if (modifiedBody.system) {
const entityContext = buildEntityContext(session.entities);
console.log(`[Claude API] Entity context added (${entityContext.length} chars)`);
modifiedBody.system += entityContext;
}
// Get conversation history and merge with current messages
const historyMessages = getConversationForAPI(sessionId);
if (historyMessages.length > 0 && modifiedBody.messages) {
// Filter out any duplicate messages (in case client sent history too)
const currentContent = modifiedBody.messages[modifiedBody.messages.length - 1]?.content;
const filteredHistory = historyMessages.filter(msg => msg.content !== currentContent);
modifiedBody.messages = [...filteredHistory, ...modifiedBody.messages];
console.log(`[Claude API] Merged ${filteredHistory.length} history + ${modifiedBody.messages.length - filteredHistory.length} new = ${modifiedBody.messages.length} total messages`);
}
} else {
console.log(`[Claude API] WARNING: Session ${sessionId} not found`);
}
}
try {
console.log(`[Claude API] Sending request to Anthropic API...`);
const fetchStart = Date.now();
const response = await fetch(`${ANTHROPIC_API_URL}${path}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": apiKey,
"anthropic-version": "2023-06-01",
},
body: JSON.stringify(modifiedBody),
});
const fetchDuration = Date.now() - fetchStart;
console.log(`[Claude API] Response received in ${fetchDuration}ms, status: ${response.status}`);
console.log(`[Claude API] Parsing response JSON...`);
const data = await response.json();
console.log(`[Claude API] Response parsed. Stop reason: ${data.stop_reason}, content blocks: ${data.content?.length || 0}`);
// Track and log token usage
if (data.usage) {
// Extract content for detailed tracking
const userMessage = requestBody.messages?.[requestBody.messages.length - 1];
const inputText = typeof userMessage?.content === 'string' ? userMessage.content : null;
const outputText = data.content
?.filter(c => c.type === 'text')
.map(c => c.text)
.join('\n') || null;
const toolCalls = data.content
?.filter(c => c.type === 'tool_use')
.map(c => ({ name: c.name, input: c.input })) || [];
const usageRecord = trackUsage(sessionId, modifiedBody.model, data.usage, {
inputText,
outputText,
toolCalls
});
console.log(`[Claude API] REQUEST USAGE: ${getUsageSummary(usageRecord)}`);
// Log cumulative session usage if session exists
if (sessionId) {
const sessionStats = getSessionUsage(sessionId);
if (sessionStats) {
console.log(`[Claude API] SESSION TOTALS (${sessionStats.requestCount} requests):`);
console.log(`[Claude API] Total input: ${sessionStats.totalInputTokens} tokens`);
console.log(`[Claude API] Total output: ${sessionStats.totalOutputTokens} tokens`);
console.log(`[Claude API] Total cost: ${formatCost(sessionStats.totalCost)}`);
}
}
}
if (data.error) {
console.error(`[Claude API] API returned error:`, data.error);
}
// If session exists and response is successful, store messages
if (sessionId && response.ok && data.content) {
const session = getSession(sessionId);
if (session) {
// Store the user message if it was new (only if it's a string, not tool results)
const userMessage = requestBody.messages?.[requestBody.messages.length - 1];
if (userMessage && userMessage.role === 'user' && typeof userMessage.content === 'string') {
addMessage(sessionId, {
role: 'user',
content: userMessage.content
});
console.log(`[Claude API] Stored user message to session`);
}
// Store the assistant response (text only, not tool use blocks)
const assistantContent = data.content
.filter(c => c.type === 'text')
.map(c => c.text)
.join('\n');
if (assistantContent) {
addMessage(sessionId, {
role: 'assistant',
content: assistantContent
});
console.log(`[Claude API] Stored assistant response to session (${assistantContent.length} chars)`);
}
}
}
const totalDuration = Date.now() - requestStart;
console.log(`[Claude API] ========== REQUEST COMPLETE (${totalDuration}ms) ==========`);
res.status(response.status).json(data);
} catch (error) {
const totalDuration = Date.now() - requestStart;
console.error(`[Claude API] ========== REQUEST FAILED (${totalDuration}ms) ==========`);
console.error(`[Claude API] Error:`, error);
console.error(`[Claude API] Error message:`, error.message);
console.error(`[Claude API] Error stack:`, error.stack);
res.status(500).json({ error: "Failed to proxy request to Claude API", details: error.message });
}
});
export default router;

213
server/api/cloudflare.js Normal file
View File

@ -0,0 +1,213 @@
import { Router } from "express";
import { getSession, addMessage, getConversationForAPI } from "../services/sessionStore.js";
import { trackUsage, getUsageSummary, formatCost, getSessionUsage } from "../services/usageTracker.js";
import { getCloudflareAccountId, getCloudflareApiToken } from "../services/providerConfig.js";
import {
claudeToolsToCloudflare,
claudeMessagesToCloudflare,
cloudflareResponseToClaude
} from "../services/toolConverter.js";
const router = Router();
/**
* Build entity context string for the system prompt
*/
function buildEntityContext(entities) {
if (!entities || entities.length === 0) {
return "\n\nThe diagram is currently empty.";
}
const entityList = entities.map(e => {
const shape = e.template?.replace('#', '').replace('-template', '') || 'unknown';
const pos = e.position || { x: 0, y: 0, z: 0 };
return `- ${e.text || '(no label)'} (${shape}, ${e.color || 'unknown'}) at (${pos.x?.toFixed(1)}, ${pos.y?.toFixed(1)}, ${pos.z?.toFixed(1)})`;
}).join('\n');
return `\n\n## Current Diagram State\nThe diagram currently contains ${entities.length} entities:\n${entityList}`;
}
// Express 5 uses named parameters for wildcards
router.post("/*path", async (req, res) => {
const requestStart = Date.now();
console.log(`[Cloudflare API] ========== REQUEST START ==========`);
const accountId = getCloudflareAccountId();
const apiToken = getCloudflareApiToken();
if (!accountId) {
console.error(`[Cloudflare API] ERROR: Account ID not configured`);
return res.status(500).json({ error: "Cloudflare account ID not configured" });
}
if (!apiToken) {
console.error(`[Cloudflare API] ERROR: API token not configured`);
return res.status(500).json({ error: "Cloudflare API token not configured" });
}
// Check for session-based request
const { sessionId, ...requestBody } = req.body;
let modifiedBody = { ...requestBody };
const model = requestBody.model;
console.log(`[Cloudflare API] Session ID: ${sessionId || 'none'}`);
console.log(`[Cloudflare API] Model: ${model}`);
console.log(`[Cloudflare API] Messages count: ${requestBody.messages?.length || 0}`);
// Build system prompt with entity context
let systemPrompt = modifiedBody.system || '';
if (sessionId) {
const session = getSession(sessionId);
if (session) {
console.log(`[Cloudflare API] Session found: ${session.entities.length} entities, ${session.conversationHistory.length} messages in history`);
// Inject entity context into system prompt
const entityContext = buildEntityContext(session.entities);
console.log(`[Cloudflare API] Entity context added (${entityContext.length} chars)`);
systemPrompt += entityContext;
// Get conversation history and merge with current messages
const historyMessages = getConversationForAPI(sessionId);
if (historyMessages.length > 0 && modifiedBody.messages) {
const currentContent = modifiedBody.messages[modifiedBody.messages.length - 1]?.content;
const filteredHistory = historyMessages.filter(msg => msg.content !== currentContent);
modifiedBody.messages = [...filteredHistory, ...modifiedBody.messages];
console.log(`[Cloudflare API] Merged ${filteredHistory.length} history + ${modifiedBody.messages.length - filteredHistory.length} new = ${modifiedBody.messages.length} total messages`);
}
} else {
console.log(`[Cloudflare API] WARNING: Session ${sessionId} not found`);
}
}
try {
// Convert to Cloudflare format
const cfMessages = claudeMessagesToCloudflare(modifiedBody.messages || [], systemPrompt);
const cfTools = modifiedBody.tools ? claudeToolsToCloudflare(modifiedBody.tools) : undefined;
// Build Cloudflare request body
const cfRequestBody = {
messages: cfMessages,
max_tokens: modifiedBody.max_tokens || 1024
};
// Only include tools if the model supports them
if (cfTools && cfTools.length > 0) {
cfRequestBody.tools = cfTools;
}
// Cloudflare endpoint: https://api.cloudflare.com/client/v4/accounts/{account_id}/ai/run/{model}
const endpoint = `https://api.cloudflare.com/client/v4/accounts/${accountId}/ai/run/${model}`;
console.log(`[Cloudflare API] Sending request to: ${endpoint}`);
console.log(`[Cloudflare API] Request body messages: ${cfMessages.length}, tools: ${cfTools?.length || 0}`);
const requestBodyJson = JSON.stringify(cfRequestBody);
console.log(`[Cloudflare API] Full request body (${requestBodyJson.length} bytes):`);
console.log(requestBodyJson);
const fetchStart = Date.now();
const response = await fetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${apiToken}`,
},
body: JSON.stringify(cfRequestBody),
});
const fetchDuration = Date.now() - fetchStart;
console.log(`[Cloudflare API] Response received in ${fetchDuration}ms, status: ${response.status}`);
console.log(`[Cloudflare API] Parsing response JSON...`);
const cfData = await response.json();
if (!cfData.success) {
console.error(`[Cloudflare API] API returned error:`, cfData.errors);
return res.status(response.status).json({
error: cfData.errors?.[0]?.message || "Cloudflare API error",
details: cfData.errors
});
}
// Convert Cloudflare response to Claude format
const data = cloudflareResponseToClaude(cfData, model);
console.log(`[Cloudflare API] Response converted. Stop reason: ${data.stop_reason}, content blocks: ${data.content?.length || 0}`);
// Track and log token usage
if (data.usage) {
// Extract content for detailed tracking
const userMessage = requestBody.messages?.[requestBody.messages.length - 1];
const inputText = typeof userMessage?.content === 'string' ? userMessage.content : null;
const outputText = data.content
?.filter(c => c.type === 'text')
.map(c => c.text)
.join('\n') || null;
const toolCalls = data.content
?.filter(c => c.type === 'tool_use')
.map(c => ({ name: c.name, input: c.input })) || [];
const usageRecord = trackUsage(sessionId, model, data.usage, {
inputText,
outputText,
toolCalls
});
console.log(`[Cloudflare API] REQUEST USAGE: ${getUsageSummary(usageRecord)}`);
// Log cumulative session usage if session exists
if (sessionId) {
const sessionStats = getSessionUsage(sessionId);
if (sessionStats) {
console.log(`[Cloudflare API] SESSION TOTALS (${sessionStats.requestCount} requests):`);
console.log(`[Cloudflare API] Total input: ${sessionStats.totalInputTokens} tokens`);
console.log(`[Cloudflare API] Total output: ${sessionStats.totalOutputTokens} tokens`);
console.log(`[Cloudflare API] Total cost: ${formatCost(sessionStats.totalCost)}`);
}
}
}
// If session exists and response is successful, store messages
if (sessionId && response.ok && data.content) {
const session = getSession(sessionId);
if (session) {
// Store the user message if it was new (only if it's a string, not tool results)
const userMessage = requestBody.messages?.[requestBody.messages.length - 1];
if (userMessage && userMessage.role === 'user' && typeof userMessage.content === 'string') {
addMessage(sessionId, {
role: 'user',
content: userMessage.content
});
console.log(`[Cloudflare API] Stored user message to session`);
}
// Store the assistant response (text only, not tool use blocks)
const assistantContent = data.content
.filter(c => c.type === 'text')
.map(c => c.text)
.join('\n');
if (assistantContent) {
addMessage(sessionId, {
role: 'assistant',
content: assistantContent
});
console.log(`[Cloudflare API] Stored assistant response to session (${assistantContent.length} chars)`);
}
}
}
const totalDuration = Date.now() - requestStart;
console.log(`[Cloudflare API] ========== REQUEST COMPLETE (${totalDuration}ms) ==========`);
res.status(response.status).json(data);
} catch (error) {
const totalDuration = Date.now() - requestStart;
console.error(`[Cloudflare API] ========== REQUEST FAILED (${totalDuration}ms) ==========`);
console.error(`[Cloudflare API] Error:`, error);
console.error(`[Cloudflare API] Error message:`, error.message);
console.error(`[Cloudflare API] Error stack:`, error.stack);
res.status(500).json({ error: "Failed to proxy request to Cloudflare API", details: error.message });
}
});
export default router;

30
server/api/index.js Normal file
View File

@ -0,0 +1,30 @@
import { Router } from "express";
import claudeRouter from "./claude.js";
import ollamaRouter from "./ollama.js";
import cloudflareRouter from "./cloudflare.js";
import sessionRouter from "./session.js";
import userRouter from "./user.js";
const router = Router();
// Session management
router.use("/session", sessionRouter);
// User features
router.use("/user", userRouter);
// Claude API proxy
router.use("/claude", claudeRouter);
// Ollama API proxy
router.use("/ollama", ollamaRouter);
// Cloudflare Workers AI proxy
router.use("/cloudflare", cloudflareRouter);
// Health check
router.get("/health", (req, res) => {
res.json({ status: "ok" });
});
export default router;

Some files were not shown because too many files have changed in this diff Show More