-
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy path.cursorrules
More file actions
1339 lines (1172 loc) · 79.5 KB
/
Copy path.cursorrules
File metadata and controls
1339 lines (1172 loc) · 79.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Citrana - Cursor Rules
## Project Overview
This is Citrana, a web application built with pure HTML5, CSS3, and JavaScript using Konva.js for canvas manipulation. The application allows users to create both South Indian and North Indian astrological charts with drag-and-drop Graha placement and comprehensive drawing tools. It runs entirely in the browser with no build process required, making it immediately deployable on GitHub Pages or any web server.
## Terminology
Use **Bhava/Bhavas**, **Graha/Grahas**, and **Rashi/Rashis** in all user-facing strings, comments, and documentation. Do not use House/Houses, Planet/Planets, or Sign/Signs/zodiac sign in prose. Internal APIs (`citrana-planet-system.js`, `addPlanetToHouse`, `house-*` Konva names) stay as-is unless explicitly refactoring.
## Technology Stack
- Frontend: Pure HTML5, CSS3, JavaScript (ES6+)
- Graphics: HTML5 Canvas API with Konva.js (self-hosted, `assets/vendor/konva.min.js` v9.3.20)
- Colour picker: JSColorPicker (self-hosted, `assets/vendor/colorpicker.iife.min.js` v1.1.0; theme in `citrana-colorpicker.js`)
- Styling: Custom CSS only
- Icons: Lucide Icons (self-hosted, `assets/vendor/lucide.min.js` v0.576.0)
- Storage: Browser `localStorage` for preferences (welcome modal, chart indicator toggles, Save Chart Only export, Zoom Step, context menu enable/disable, Graha Library visibility, debug opt-out)
- Analytics: Google Analytics and Google Tag Manager
- No build process required - runs entirely in browser
## Complete Project Structure
Classic tree view for Cursor rules context (see [AGENT.md](AGENT.md) for a markdown table version).
```
Soothsayer-Citrana/
├── index.html # Main entry (~607 lines); viewport-fit=cover; PWA meta; #welcome-modal-backdrop + Welcome modal (6-step quick start, loading bar); Help Guide (`.help-intro`, `.help-subsection-title`); `#confirmation-modal` (confirm / alert / open / save-as modes; `#confirmation-filename-input`); Canvas Items modal; Options modal (Zoom Step); toolbar + Edit UI scroll wraps
├── robots.txt
├── sitemap.xml
├── assets/
│ ├── css/
│ │ └── styles.css # Complete styling system (~3280 lines); `@font-face` Shantell Sans; primary @media (max-width: 768px) block + post-base mobile overrides; JSColorPicker --cp-* theme; `.items-*` panel; `#graha-library.graha-library-hidden`; `.graha-library-dragging`; `.page-dots-chevron`; `.confirmation-modal--*` / `.confirmation-filename-*`; `.help-intro` / `.help-subsection-title`; `.citrana-laser-canvas`; `.citrana-canvas-hints`; `.welcome-modal-backdrop`; `body.welcome-modal-open`; `body.presentation-view`; `.toolbar-scroll-*`
│ ├── fonts/ # Self-hosted Shantell Sans (Regular, Bold, Italic, BoldItalic) + OFL.txt
│ ├── js/ # 21 modules — all citrana-* prefixed
│ │ ├── citrana-annotation-fonts.js # Normal and hand-written annotation fonts (~159 lines)
│ │ ├── citrana-app.js # Main application coordinator (~2517 lines)
│ │ ├── citrana-arrow.js # Unified filled-arrow geometry (~185 lines)
│ │ ├── citrana-canvas-hints.js # Blank-canvas onboarding hint overlay (~397 lines)
│ │ ├── citrana-chart-coordinator.js # Chart type management (~322 lines)
│ │ ├── citrana-chart-templates-north.js # North Indian chart logic (~1015 lines)
│ │ ├── citrana-chart-templates-south.js # South Indian chart logic (~989 lines)
│ │ ├── citrana-colorpicker.js # JSColorPicker theme and helpers (~388 lines)
│ │ ├── citrana-context-menu.js # Context menu system (~742 lines)
│ │ ├── citrana-debug.js # Contributor debug logging (~13 lines; on by default)
│ │ ├── citrana-device.js # Shared touch, mobile UA, and viewport helpers (~39 lines)
│ │ ├── citrana-drawing-tools.js # Drawing tools implementation (~3270 lines)
│ │ ├── citrana-edit-ui.js # Edit interface controls (~1030 lines)
│ │ ├── citrana-history.js # Unified undo/redo timeline (~94 lines)
│ │ ├── citrana-items-menu.js # Canvas Items panel — chart/Bhava/Graha/Annotation actions (~861 lines)
│ │ ├── citrana-laser.js # Ephemeral laser pointer Canvas overlay (~248 lines)
│ │ ├── citrana-planet-system.js # Graha library and drag-drop (~1000 lines)
│ │ ├── citrana-rashis.js # Shared Rashi names, Lucide zodiac icons, grid numbers (~49 lines)
│ │ ├── citrana-selection.js # Selection Pill (~99 lines)
│ │ ├── citrana-session.js # Save/open .citrana.json session files (~270 lines)
│ │ └── citrana-zoom.js # Zoom step presets (~58 lines)
│ ├── vendor/
│ │ ├── konva.min.js # Konva 9.3.20 (self-hosted; loaded in <head>)
│ │ ├── lucide.min.js # Lucide 0.576.0 (self-hosted)
│ │ ├── colorpicker.iife.min.js # JSColorPicker 1.1.0 (self-hosted)
│ │ └── colorpicker.min.css # JSColorPicker 1.1.0 stylesheet
│ ├── images/ # 19 files (logos, demo GIFs, browser screenshot, hint-*.png)
│ │ ├── soothsayer_citrana_social-preview.jpg
│ │ ├── Soothsayer-Citrana-Full-Logo-Black.png / -White.png
│ │ ├── Soothsayer-Logo-Black.png / Soothsayer-Logo-White.png
│ │ ├── citrana-browser-screenshot.png
│ │ ├── hint-start-message.png / hint-graha-library.png / hint-main-toolbar.png / hint-zoom-bar.png / hint-help.png
│ │ └── demo-*.gif # English, Tamil, Hindi, tldraw demos
│ ├── svgs/
│ │ ├── north-indian.svg
│ │ └── south-indian.svg
│ └── favicon/ # 29 files
│ ├── favicon.ico
│ ├── manifest.json # PWA manifest (display: standalone)
│ ├── browserconfig.xml
│ ├── apple-icon-*.png # 12 variants
│ ├── android-icon-*.png # 6 variants
│ ├── favicon-*.png # 5 variants
│ └── ms-icon-*.png # 4 variants
├── .github/
│ └── workflows/
│ ├── static.yml # GitHub Pages deploy with minification (push to main)
│ └── codeql.yml # CodeQL security analysis
├── AGENT.md # Comprehensive documentation (~1355 lines)
├── ARCHITECTURE.md # System architecture and data flows (~600 lines)
├── .cursorrules # This Cursor IDE configuration (~1340 lines)
├── CHANGELOG.md # Version history (~109 lines)
├── README.md # Project readme (~249 lines)
├── LICENSE # MIT License
├── SECURITY.md # Security policy
└── .gitignore # Git ignore rules
```
> Line counts are approximate; run `wc -l` after significant edits. Update About modal version in `index.html` on each release. **Script load order** is dependency order in `index.html` (not alphabetical) — see [AGENT.md](AGENT.md#script-load-order-indexhtml).
Keep this file in sync with [AGENT.md](AGENT.md) and [ARCHITECTURE.md](ARCHITECTURE.md).
## CSS Styling and Theme System
### Design Philosophy
Citrana uses a clean, minimalist design approach with a consistent light theme throughout the application. The design prioritises readability, accessibility, and professional presentation suitable for educational and astrological work.
### Colour Scheme
- Primary Background: Pure white (#ffffff)
- Primary Text: Black (#000000)
- Secondary Text: Dark grey (#495057, #374151)
- Muted Text: Medium grey (#6b7280)
- Borders: Black (#000000)
- Interactive Elements: Black background with white text on hover/active states
- Graha Colours: Individual Graha-specific colours (Sun: #e2792e, Moon: #868484, etc.)
### Typography
- Primary Font Stack: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif
- Font Sizes: Responsive scaling from 12px to 18px based on device
- Font Weights: Normal (400) and Bold (700)
- Line Heights: Optimised for readability across devices
### Layout System
The application uses a floating UI system with absolute positioning for all interactive elements:
#### Floating Top Toolbar
- Positioned at top centre with 20px margin
- White background with black border
- Box shadow for depth (0 4px 12px rgba(0, 0, 0, 0.15))
- Rounded corners (8px border-radius)
- Button groups (left to right): **Undo/Redo** (`#undo-btn`, `#redo-btn`), Select/Hand, drawing tools, export/transparency/**Export** (`#export-btn`), **Save Session** / **Open Session** (`#save-session-btn`, `#open-session-btn`, `#open-session-input`), **Options** (`#options-btn`)
- Mobile `≤768px`: horizontal scroll viewport (`#toolbar-scroll-viewport`) with chevron nudges (`#toolbar-scroll-prev`, `#toolbar-scroll-next`) when tools overflow
- Responsive design with smaller buttons on mobile
#### Floating Graha Library
- Desktop: top left with `--ui-inset` + safe area; max-width 280px
- Mobile: bottom stack above zoom bar (`--ui-bottom-stack` + safe area); 320px width
- White background with black border (`border-radius: 0.75rem`); paging dots (`.page-dots-track`); mobile dots-bar swipe with grey chevron hints (`.page-dots-chevron`); keyboard **1**–**5**
- Markup: `#graha-library` > `.planet-library-header` + `#planet-library.planet-grid` + `.page-dots` (dots injected by JS)
- Styles in CSS only — no inline header/grid styles in `index.html`
- **No scrolling** — `.planet-grid` uses `overflow: visible` (no `max-height` cap)
- Desktop grid: `repeat(auto-fit, minmax(80px, 1fr))`; Graha cells 80×40px
- Mobile grid (≤768px): 6 columns × 2 rows; compact header/grid/dots padding; cells 30px tall, 7px font, `word-break: break-word` for long Upagraha names on Page 5
- Library cells display `planet.fullName` via `createPlanetLibrary()` (abbreviations used on chart placement)
- **Visibility toggle:** Canvas Items → **Canvas** → **Graha Library** (On/Off; persisted in `localStorage.citrana_graha_library_enabled`; default on); `#graha-library.graha-library-hidden` hides the panel — independent of Presentation View
- **Panel repositioning:** drag `.planet-library-header` to move the library; `.graha-library-dragging` raises `z-index` above toolbar, zoom bar, and corner buttons while dragging (below modals)
- **Reset position:** Canvas Items → **Canvas** → **Reset Graha Library Position** (`orbit` row icon; `move` action button; meta **Default Layout**; clears inline position/size overrides — top-left desktop, bottom-centre mobile); no confirmation; not undoable
#### Floating Zoom Controls
- Desktop: bottom left (`--ui-inset` + safe area)
- Mobile: bottom centre (288px width); includes `#zoom-in`, `#zoom-out`, `#reset-zoom`, `#zoom-lock`, `#zoom-level`, divider, **`#items-menu-btn`** (layers icon — **Canvas Items** panel)
- Select/Hand remain in the **top toolbar only** (not in the zoom bar)
- Block height: `--zoom-controls-block-height` (48px desktop; **50px mobile** — includes 1px border × 2 so outer height matches Help/About)
- Mobile: explicit `height: var(--zoom-controls-block-height)` on `.floating-zoom-controls`
- `#zoom-lock`: default locked; `lock` / `lock-open` Lucide icons; disables zoom in/out when locked
- White background with black border
#### Help and About Buttons
- Help (`#help-btn`): top-right on desktop; bottom-left on mobile — aligned with zoom bar bottom edge
- About (`#about-btn`): bottom-right on all viewports
- Size: `--corner-btn-size` (48px desktop; **50px mobile** — matches zoom bar outer height); mobile `border-radius: 8px` to match zoom bar
- Icons: Help Lucide icon at 50% of button; About logo at 62.5% of button
- **CSS cascade:** mobile Help/About reposition rules live in a separate `@media (max-width: 768px)` block **after** base `.help-btn` / `.about-btn` styles (not only in the main mobile block)
- Hidden when `body.presentation-view` is active (Presentation View); Graha bar (`.floating-text-edit-controls`) and drawing Edit UI (`.floating-edit-ui`) hidden the same way
#### Floating Edit UI
- Context-sensitive positioning at bottom centre
- White background with black border
- Tool-specific controls; text/heading: size, bold, italic, **Normal** / **Hand-written**, alignment, colour
- Mobile ≤768px: `[◀] [viewport] [▶]` chevron scroll (`#edit-ui-scroll-prev` / `#edit-ui-scroll-viewport` / `#edit-ui-scroll-next`; reuses `.toolbar-scroll-*` styles)
- Mobile-optimised touch targets
### Component Styling
#### Buttons
- 40px x 40px touch targets (desktop)
- 36px x 36px on mobile devices
- Transparent background with black text
- Hover state: black background with white text
- Active state: scale transform (0.95)
- Toolbar and zoom bar: `:disabled` buttons at reduced opacity; no hover invert
- Smooth transitions (0.2s ease)
#### Input Fields
- Black border with focus state
- Smooth border colour transitions
- Mobile-optimised with larger touch targets
- Safari-specific fixes for colour inputs
#### Context Menus
- White background with black border
- Hover states with black background and white text
- Submenu support with proper nesting
- Mobile-optimised with larger touch targets
#### Modals
- Centred positioning with backdrop
- White background with black border
- Responsive sizing for different screen sizes
- Smooth animations for show/hide states
- `role="dialog"`, `aria-modal="true"`, `aria-labelledby`, `aria-describedby`; `aria-hidden` toggled by `openModal()` / `closeModal()`
- **Confirmation** (`#confirmation-modal`): confirm, alert, open, and save-as modes; `#confirmation-filename-input` for Save As
- Help intro: `#help-modal-description` uses `.help-modal-description` (margin before first section; not `.help-instructions` wrapper)
- Mobile About/Welcome: compact typography; `overflow: hidden` (no scroll); mobile `@media` blocks **after** base modal CSS
- Welcome: split z-index backdrop (8999) / hints (9000) / dialog (9100); `body.welcome-modal-open` keeps chrome behind grey backdrop on mobile Safari
- **Escape** dismisses open modals via `dismissActiveModalOnEscape()` (operation progress excepted)
- **Tab focus trap** via `trapModalFocus()`; focus moves to close button on open and restores on close (`_modalFocusStack`)
#### Canvas
- `#canvas-container`: `role="application"` with descriptive `aria-label`
### Responsive Design
The application uses a mobile-first approach with three main breakpoints:
#### Desktop (769px and above)
- Full toolbar with all controls visible
- Larger touch targets and spacing
- Side-by-side layout for complex controls
#### Tablet (768px and below)
- Compact toolbar with essential controls
- Reduced button sizes (36px x 36px)
- Optimised spacing for touch interaction
#### Mobile (600px and below)
- Minimal toolbar with core functions
- Full-width modals and controls
- Enhanced touch targets for accessibility
- Simplified navigation patterns
### Safari and Mobile Optimisations
- WebKit-specific fixes for colour inputs
- Tap highlight colour removal for clean interaction
- Input zoom prevention on mobile devices
- Touch event optimisation for smooth performance
- Safari-specific toolbar visibility handling (`setupSafariToolbarFix()` — `focusin`/`focusout`, `resize`, `scroll`, and `visualViewport` listeners; no polling timer)
### iOS Standalone PWA Layout (2.0)
- `viewport-fit=cover` in `index.html`; `manifest.json` `display: standalone`
- CSS safe-area variables: `--sat`, `--sar`, `--sab`, `--sal`; `--ui-inset` (20px), `--ui-inset-sm` (10px), `--ui-bottom-pad` (8px mobile / 4px standalone PWA)
- Layout tokens: `--ui-bottom-stack` (60px desktop token — 48px zoom + 12px gap above Graha library), `--zoom-controls-block-height` (48px), `--corner-btn-size` (48px); **mobile `≤768px` overrides** in main mobile block: 50px chrome height, `--ui-bottom-stack: 62px`
- `body { position: fixed; inset: 0 }` and `.app-container { position: absolute; inset: 0 }` — avoids iOS `100dvh` bottom gap
- Floating UI uses `calc(inset + safe-area)`; Graha library repositions to bottom stack on mobile (`--ui-bottom-stack`)
- `app.handleResize()` uses `visualViewport` when available
- Mobile (`≤768px`): arrow, line, pen, and laser toolbar buttons visible with horizontal scroll chevrons when tools overflow; Help moves to bottom-left; **Canvas Items** button in zoom bar
**CSS responsive note:** Most tablet/mobile rules are in one `@media (max-width: 768px)` block near the top of the responsive section. Overrides that must beat later base component rules (Help/About position, About/Welcome/Confirmation/Export modal sizing) use **additional** `@media (max-width: 768px)` blocks placed **after** those base selectors in `styles.css`.
### Accessibility Features
- High contrast colour scheme
- Adequate touch target sizes (minimum 36px)
- Keyboard navigation support
- Screen reader friendly structure (`role`, `aria-*` on canvas and modals)
- Focus indicators for interactive elements
- Modal focus management: trap Tab inside dialogs, restore focus on close
- Icon-only buttons use `aria-label`; zoom lock uses `aria-pressed`
### Performance Optimisations
- Hardware-accelerated animations
- Efficient CSS selectors
- Minimal DOM manipulation
- Optimised canvas rendering
- Optimised resize handlers
## Core Components Architecture
For system design, module boundaries, data flows, and extension points, see ARCHITECTURE.md.
### Main Application (assets/js/citrana-app.js - ~2517 lines)
The central coordinator that manages all application components and lifecycle.
Key Responsibilities:
- Initialises Konva.js stage and layer
- Coordinates all component interactions
- Manages tool selection and drawing state
- Handles keyboard shortcuts and event listeners
- Manages unified undo/redo via `CitranaHistory` (`citrana-history.js`)
- Handles chart export (full viewport or chart-only crop via Options)
- Manages chart display options modal and `localStorage` preferences (indicators, Save Chart Only, **Zoom Step**)
- **Save/Open Session** via `CitranaSession` (`.citrana.json` files only); **Save As** dialog before save; **Open Session** intro dialog before file picker; shared progress dialog; in-app alerts for session errors
- Initialises **Canvas Items** panel (`CitranaItemsMenu`) and toolbar horizontal scroll
- Initialises **Canvas onboarding hints** (`CitranaCanvasHints`); `notifyCanvasContentCreated()`, `refreshCanvasHints()`
- Provides mobile touch support and Safari compatibility
- Manages zoom controls, zoom lock (default locked), zoom level display, canvas resize (`visualViewport`), and **Presentation View** (`body.presentation-view` chrome hiding)
- Manages modal open/close, focus trap, and Escape dismiss for all overlays (including operation progress and **Canvas Items** modal)
Key Methods:
- `init()`: Application initialisation; loads `this.options` from `localStorage`
- `setupCanvas()`: Konva stage; `scaleXChange`/`scaleYChange` → `updateZoomLevel()`
- `setupToolbarScroll()`: Horizontal toolbar overflow with `#toolbar-scroll-wrap`, chevrons, and mobile edge fades (`toolbar-scroll-fade-start` / `toolbar-scroll-fade-end`)
- `setupKeyboardShortcuts()`: Tool/action shortcuts; **I** toggles **Canvas Items** panel (open/close; **I** again closes when Canvas Items is open); **Escape** → `dismissActiveModalOnEscape()`; **Tab** → `trapModalFocus()` when a modal is open; otherwise blocked while inline Graha/text editors are focused or `isModalBlockingShortcuts()` (Help, Options, About, Welcome, Confirmation, Canvas Items, operation progress)
- `openModal(modal)` / `closeModal(modal)`: Toggle `.active` and `aria-hidden`; push/pop focus stack; focus close button on open; toggles `#welcome-modal-backdrop` and `body.welcome-modal-open` for welcome
- `closeWelcomeModal()`: Welcome close + `localStorage.citrana_welcome_seen`; refreshes canvas hints
- `notifyCanvasContentCreated()` / `refreshCanvasHints()`: Dismiss or reposition onboarding hints
- `getActiveModal()` / `dismissActiveModalOnEscape()`: Topmost modal; Escape dismiss (operation progress not dismissible)
- `getModalFocusableElements()` / `getModalInitialFocusElement()` / `focusModalEntry()` / `trapModalFocus()`: Modal focus trap
- `pushModalFocus()` / `popModalFocus()`: `_modalFocusStack` for focus restore
- `isModalBlockingShortcuts()`: Returns true when any modal overlay is open
- `isTouchDevice()`: Delegates to `CitranaDevice.isTouchDevice()`
- `setTool()`: Tool routing to drawing tools and hand mode
- `zoomIn()` / `zoomOut()` / `zoomToFit()`: Delegate to `ChartCoordinator` (`zoomIn`/`zoomOut` no-op when locked; `zoomToFit` always works)
- `toggleZoomLock()` / `updateZoomLockUI()`: Toggle `zoomLocked`; swap `#zoom-lock` icon (`lock` / `lock-open`); disable `#zoom-in` / `#zoom-out`
- `updateZoomLevel()`: Updates `#zoom-level` from `stage.scaleX()`
- `handleResize()`: Stage size from `visualViewport` or container; `CitranaLaser.resize()` syncs laser overlay
- `handleWheel()`: Desktop wheel zoom about pointer when unlocked; early return when locked (no `preventDefault`)
- `exportChart()` / `runExportChart(fileName)` / `finalizeExportImage()`: **Save As** dialog first, then PNG export (`pixelRatio: 2`); full stage or chart-only crop when `options.saveChartOnly`; `isExporting` guard; shared `#export-progress-modal`
- `setNorthHideIndicators(hide)` / `setSouthHideIndicators(hide)`: Persist indicator toggles; apply to active chart template
- `setSaveChartOnly(enabled)` / `applySaveChartOnlyTransparency()` / `updateTransparencyToggleUI()`: Chart-only export; when enabled forces transparent export and locks `#toggle-transparency-btn`; when disabled restores `exportWithWhiteBg = true` and re-enables the toggle
- `recordHistory()` / `captureHistoryState()` / `restoreHistoryState()`: Undo timeline integration; `restoreHistoryState()` saves/restores stage scale and position (chart reload via `loadChartData()` calls `clearChart()`, which resets the viewport — restored after reload); templates use `skipZoomToFit: true` on history restore
- `undo()` / `redo()` / `updateHistoryButtons()`: Delegate to `this.history`; sync `#undo-btn` / `#redo-btn` disabled state
- `clearChart()` / `resetChart()` / `resetDrawings()`
- `isPresentationView()` / `togglePresentationView()`: Toggle `presentationView` flag and `body.presentation-view` class; hides toolbar, zoom bar, Graha library, Help, About, Graha bar, and drawing Edit UI; dismisses open edit sessions on enter; not undoable
- `clearWelcomeLoadingInterval()` / `showWelcomeModal()`: First-visit welcome (`citrana_welcome_seen`); simulated `.welcome-loading-fill` progress; status at <20% / <40% / <60% / <80% / <100% (**Ready!** at 100% only); timer cleared on close or completion; manual dismiss
- Mouse/touch handlers: `handleMouseDown/Move/Up`, `handleTouchStart/Move/End`; `handleTouchStart/Move` call `drawingTools.shouldPreserveTouchDrag()` before `preventDefault` (Grahas, drawings, pen pick rects, control points, active pen drag); empty-canvas `mousedown`/`tap` → `clearCanvasSelection()`; `_selectPointerDownOnDrawing` prevents stage `tap` from clearing selection when pointer down was on a drawing but release was elsewhere
- `isAnnotationTarget()`: Pen tool — blocks starting a stroke only on existing annotations, not on chart Bhavas
- `clearCanvasSelection()` / `getCanvasSelection()` / `notifyCanvasSelectionChanged()`: Unified selection for Canvas Items panel row highlight
- `setupSafariToolbarFix()`: Touch Safari UI visibility restore on focus/viewport events (`visualViewport` resize/scroll)
- `showConfirmationDialog()` / `showAlertDialog()` / `showOpenSessionDialog()` / `showSaveAsDialog()` / `_setConfirmationModalLayout()`: Reuse `#confirmation-modal` for confirm, alert, open, and save-as modes (`confirmation-modal--confirm|alert|open|save-as`); `#confirmation-filename-input` for Save As; `getModalInitialFocusElement()` focuses filename field in save-as mode
- `buildChartExportFileName()`: Default `citrana-chart-{timestamp}.png` for export Save As
- `showProgressModal()` / `updateProgressModal()` / `hideProgressModal()` / `completeProgressModal()` / `failProgressModal()`: Shared operation progress dialog (`#export-progress-modal`); dynamic title; focus trap and `aria-busy`; not dismissible via Escape
- `showExportProgress()` / `updateExportProgress()` / `hideExportProgress()`: Export wrappers for the shared progress dialog
- `saveSession()` / `runSaveSession(fileName)` / `openSessionFromFile()` / `applyImportedSession()` / `restoreSessionState()`: `.citrana.json` via `CitranaSession`; Save As before save; open intro before file picker; progress modal during capture/restore; confirm before replace; `isSessionBusy` blocks concurrent save/open/export; `history.resetToState()` on import
- `hasSessionContent()`: Whether chart or drawings exist before session replace prompt
Keyboard shortcuts: `V` Select, `A` Arrow, `L` Line, `P` Pen, `K` Laser Pointer (when available), `T` Text, `H` Hand, `1`–`5` Graha Library pages, `I` Canvas Items (toggle open/close), `Ctrl+Z`/`Cmd+Z` undo, `Ctrl+Y`/`Ctrl+Shift+Z`/`Cmd+Shift+Z` redo, `+`/`-` zoom (when unlocked), `0` zoom to fit, `Delete` remove selected Graha or delete selected drawing (Select tool), `?`/`/` Help, **Escape** close modal. No Heading shortcut. Ignored when a modal is open (except **Escape**/**Tab** for modal UX, and **I** to close Canvas Items when it is open) or Graha/text inline editor is focused.
### Canvas Hints (assets/js/citrana-canvas-hints.js - ~397 lines)
Blank-canvas onboarding DOM overlay (`CitranaCanvasHints`).
Key Responsibilities:
- PNG hints (2084×501 artboard) anchored to Graha library, toolbar, zoom bar, and Help via `arrowTip` / `attachEdge`
- Centred start stack: full-opacity logo + semi-transparent start message
- Staggered fade-in; hidden after chart/Graha/annotation or in Presentation View
- Welcome layering: `#welcome-modal-backdrop` (8999) → hints (9000) → `#welcome-modal` dialog (9100); `body.welcome-modal-open` lowers toolbar/zoom/Help/About/library/edit bars to z-index 800 so Safari mobile `!important` chrome (9999–10000) stays behind the grey backdrop
Key Methods:
- `init(app)`, `update()`, `scheduleUpdate()`, `dismiss()`, `shouldShow()`
Wired from `app.setupComponents()`, `notifyCanvasContentCreated()`, `refreshCanvasHints()`, chart create, Graha drop, and drawing tools.
### History Engine (assets/js/citrana-history.js - ~94 lines)
Unified undo/redo timeline for the entire session.
Key Responsibilities:
- Stores labelled snapshots (`entries[]`, `index`, `maxSteps: 50`)
- Deep-clones state on `record()`; suppresses recording during restore (`_restoring`)
Key Methods:
- `record(label)`, `undo()`, `redo()`, `canUndo()`, `canRedo()`
Wired in `app.setupComponents()` with `captureHistoryState()` / `restoreHistoryState()`.
### Chart Coordinator (assets/js/citrana-chart-coordinator.js - ~322 lines)
Manages the relationship between South Indian and North Indian chart templates.
Key Responsibilities:
- Routes operations to appropriate chart template
- Manages chart type switching
- Provides unified interface for chart operations
- In-session chart serialisation (`getChartData` / `loadChartData` for undo snapshots)
- Stage zoom (`zoomIn`, `zoomOut`, `zoomToFit`) via `CitranaZoom.computeNextScale()` and zoom level display delegation
- Pointer-to-bhava hit-test for Graha library drops
Key Methods:
- `createSouthIndianChart()` / `createNorthIndianChart()`: Initialise layouts
- `setLagnaHouse(houseNumber, options?)`: Set ascendant; `options.skipSnapshot` suppresses undo step during `loadChartData()` restore (both templates)
- `getChartData()` / `loadChartData()`: Serialise / restore chart (in-session)
- `stagePointerToChartCoords()` / `clientToChartCoords()`: Map pointer to chart space
- `findHouseAtChartPoint()` / `findHouseAtPointer()` / `findHouseAtClientPoint()`: Drop targeting
- `zoomIn()` / `zoomOut()` / `zoomToFit()` / `updateZoomLevel()` — `zoomToFit()` routes by `currentChartType` (not group existence)
- `hasActiveChart()` / `getExportCropRect()` / `unionClientRects()`: Chart-only PNG crop bounds in stage pixels
- `addPlanetToHouse()`, `clearAllPlanets()`, `clearChart()`
- `getStage()`: Konva stage (used by Graha library drop coords)
**Removed:** `setFirstHouse()`, `getDropZones()`, `highlightHouse()`, `clearHighlight()`, `renumberHouses()` (use template methods directly).
### Citrana Zoom (assets/js/citrana-zoom.js - ~58 lines)
Zoom step presets shared by app wheel zoom, coordinator zoom buttons, keyboard **+**/**−**, and `.citrana.json` session `options.zoomStep`.
- `resolveZoomStep(value)` — `fine` (default), `small`, `medium`, `large`
- `computeNextScale(oldScale, direction, zoomStep)` — fine = ±1%; other steps use ~10% / ~20% / ~25% multipliers; clamped 0.1–5
Load after chart templates, before `citrana-chart-coordinator.js` and `citrana-session.js`.
### South Indian Chart Template (assets/js/citrana-chart-templates-south.js - ~989 lines)
Handles the traditional South Indian chart layout with 4x4 grid structure.
Key Responsibilities:
- Creates 4x4 grid layout with centre empty space
- Manages Bhava numbering and Lagna indicators
- Handles Graha placement and text scaling
- Provides Bhava highlighting and selection
- Manages Rashi and Bhava number boxes
- Handles Bhava and Graha right-clicks (`stopPropagation`) for context menus
Key Features:
- Traditional square grid layout
- Centre empty space for annotations
- Lagna indicator with diagonal line
- Dynamic Graha text sizing
- Bhava renumbering based on Lagna position
- Touch and mouse interaction support
Key Methods:
- `createSouthIndianChart(options?)`: Build chart layout; `options.skipZoomToFit` skips fit on undo restore
- `createHouse()`: Create individual Bhava elements
- `addPlanetToHouse()`: Place Grahas in Bhavas
- `setLagnaHouse(houseNumber, options?)`: Set ascendant with visual indicator; `skipSnapshot` for undo restore
- `renumberHouses()`: Update Bhava numbering
- `getBhavaNumberForHouse()`: Get Bhava number (1–12 from Lagna) for a fixed grid cell
- `findHouseAtChartPoint()`: Rectangle hit-test (with nearest-Bhava fallback) for library drops
- `highlightHouse()` / `clearHighlight()`: Visual Bhava selection (`#f3f4f6`)
- `selectPlanet()` / `clearSelectedPlanet()`: Graha selection via `CitranaSelection`
- Rashi number boxes use `CitranaRashis.getNumberForHouseIndex()`; mobile fit uses `CitranaDevice.isCompactViewport()` / `isMobileUA()`
- `setSouthIndicatorsVisible(visible)` / `applySouthIndicatorsPreference()`: Show or hide lagna line and bhava/rashi boxes per `app.options`
- `zoomToFit()`: Fit chart using **local bounds**; compact viewport (`≤600px`): fixed **65%** scale; desktop: computed fit (`scaleFactor=0.7`)
### North Indian Chart Template (assets/js/citrana-chart-templates-north.js - ~1015 lines)
Handles the diamond-shaped North Indian chart layout with polygon-based Bhavas.
Key Responsibilities:
- Creates diamond-shaped polygon layout
- Manages complex Bhava positioning
- Handles tiny Rashi number boxes
- Provides advanced Rashi numbering logic (`lagnaHouseNorth` stores Rashi 1–12)
- Manages Graha placement in polygon Bhavas with per-Graha `rashiNumber`
- Handles Bhava and Graha right-clicks (`stopPropagation`) for context menus
Key Features:
- Diamond-shaped polygon layout
- Precise Bhava positioning using SVG coordinates
- Tiny Rashi number boxes with exact positioning
- Advanced Rashi numbering system
- Dynamic Bhava renumbering
- Polygon-based hit detection
Key Methods:
- `createNorthIndianChart(options?)`: Build diamond layout; `options.skipZoomToFit` skips fit on undo restore
- `addPlanetToHouse()`: Place Grahas in polygon Bhavas
- `setLagnaHouse(houseNumber, options?)`: Set Lagna rashi; renumber and `repositionPlanetsForNewLagna()`; `skipSnapshot` for undo restore
- `renumberHouses()`: Update Bhava numbering
- `isPointInPolygon()`: Hit detection for polygon Bhavas
- `getRashiNumberForHouse()`: Rashi calculation
- `findHouseAtChartPoint()`: Polygon hit-test (with nearest-centroid fallback)
- `highlightHouse()` / `clearHighlight()`: Visual Bhava selection (`#f3f4f6`)
- `selectPlanet()` / `clearSelectedPlanet()`: Graha selection via `CitranaSelection`
- `raiseDrawingsAboveChart()` / `syncNorthChartLayerOrder()`: Keep annotations above chart layer
- `setNorthIndicatorsVisible(visible)` / `applyNorthIndicatorsPreference()`: Show or hide `tinyBoxGroupNorth` per `app.options`
- Lagna logging uses `CitranaRashis.getName()`; mobile fit uses `CitranaDevice.isCompactViewport()`
- `zoomToFit()`: Fit chart using **local bounds**; compact viewport (`≤600px`): fixed **82%** scale; desktop: computed fit (`scaleFactor=0.7`, `extraTopMargin=-50`)
### Graha System (assets/js/citrana-planet-system.js - ~1000 lines)
Manages the floating Graha library and drag-and-drop functionality with paging system.
Key Responsibilities:
- Creates and manages floating Graha library UI with paging
- Implements drag-and-drop for Graha placement
- Handles touch and mouse interactions
- Manages Graha data and visual representations
- Provides drop zone detection and validation
- Implements mobile-friendly drag preview
- Manages paging navigation for desktop and mobile
Key Features:
- Floating, draggable Graha library with paging
- 60 Grahas across five pages (12 per page)
- Desktop navigation with clickable page dots; keyboard **1**–**5** on all viewports
- Mobile paging: swipe left/right on **`.page-dots` bar only** (grey chevron hints; dots remain tappable; does not move library panel or start Graha drag)
- **Visibility toggle:** show/hide floating library via Canvas Items → **Graha Library** (On/Off; `localStorage.citrana_graha_library_enabled`; default on; `#graha-library.graha-library-hidden`)
- **Panel drag:** header drag repositions library; `setGrahaLibraryDragging()` toggles `.graha-library-dragging` (z-index **10001** while dragging)
- **Reset position:** `resetGrahaLibraryPosition()` — Canvas Items → **Reset Graha Library Position**; clears inline layout overrides; works when library hidden
- Drag preview with visual feedback
- Touch and mouse support
- Drop zone validation
- Mobile-optimised interactions
- Library cells render `fullName` labels; grid layout has no scroll (desktop auto-fit columns; mobile 6×2)
- Lg: Lagna (Ascendant)
- Su: Sun
- Mo: Moon
- Me: Mercury
- Ve: Venus
- Ma: Mars
- Ju: Jupiter
- Sa: Saturn
- Ra: Rahu
- Ke: Ketu
- Md: Maandi
- Cu: Custom
Graha Library - Page 2 (Jaimini Karakas):
- AK: Atmakaraka
- AmK: Amatyakaraka
- BK: Bhratrikaraka
- MK: Matrikaraka
- PK: Pitrikaraka
- GK: Gnatikaraka
- DK: Dara Karaka
- AL: Arudha Lagna
- UL: Upapada Lagna
- KL: Karakamsa Lagna
- HL: Hora Lagna
- SL: Sree Lagna
Graha Library - Page 3 (Tamil Grahas):
- ல: லக்கினம் (Lagna)
- சூ: சூரியன் (Sun)
- சந்: சந்திரன் (Moon)
- பு: புதன் (Mercury)
- சுக்: சுக்ரன் (Venus)
- செவ்: செவ்வாய் (Mars)
- குரு: குரு (Jupiter)
- சனி: சனி (Saturn)
- ரா: ராகு (Rahu)
- கே: கேது (Ketu)
- மா: மாந்தி (Maandi)
- ப: பயன் (Custom)
Graha Library - Page 4 (Hindi Grahas):
- लग्न: लग्न (Lagna)
- सूर्य: सूर्य (Sun)
- चंद्र: चंद्र (Moon)
- बुद्ध: बुद्ध (Mercury)
- शुक्र: शुक्र (Venus)
- मंगल: मंगल (Mars)
- गुरु: गुरु (Jupiter)
- शनि: शनि (Saturn)
- राहु: राहु (Rahu)
- केतु: केतु (Ketu)
- मांदी: मांदी (Maandi)
- कस: कस्टम (Custom)
Graha Library - Page 5 (Upagrahas & Outer Grahas):
- Dh: Dhuma
- Vy: Vyatipata
- Pv: Parivesha
- Ic: Indra Chapa
- Uk: Upaketu
- Kl: Kala
- Mr: Mrityu
- Ap: Ardha Prahara
- Yg: Yama Ghantaka
- Ur: Uranus
- Ne: Neptune
- Pl: Pluto
Key Methods:
- `init()`: Initialise Graha library
- `isGrahaLibraryEnabled()` / `setGrahaLibraryEnabled()` / `toggleGrahaLibrary()` / `applyGrahaLibraryVisibility()` — persisted in `localStorage.citrana_graha_library_enabled`
- `getGrahaLibraryItemsTitle()` / `getGrahaLibraryItemsMeta()` / `getGrahaLibraryToggleActionLabel()` — Canvas Items panel **Graha Library** row labels
- `getGrahaLibraryResetItemsTitle()` / `getGrahaLibraryResetItemsMeta()` / `getGrahaLibraryResetActionLabel()` — Canvas Items **Reset Graha Library Position** row copy
- `resetGrahaLibraryPosition()` / `setGrahaLibraryDragging()` — default layout restore and drag stacking
- `setupLibraryEventListeners()` / `handleLibraryDragStart/Move/End()` / `handleLibraryTouchStart/Move/End()` — floating panel reposition
- `createPlanetLibrary()`: Build UI elements with paging
- `createPageDots()`: Create page dots in `.page-dots-track` with mobile chevron hints
- `setupSwipeEvents()`: Horizontal swipe on `.page-dots` only (`pageDotsEl`)
- `goToPage()`: Navigate between pages
- `setupDragAndDrop()`: Configure drag functionality
- `handleDragStart/Move/End()`: Drag interaction handling
- `handleTouchStart/Move/End()`: Touch interaction handling
- `handleDrop()` / `handleMobileDrop()`: Place Graha on chart (one-shot selected bhava or pointer hit-test)
- `clearSelectedBhavaDropTarget()`: Clear `window.selectedBhavaSouth` / `window.selectedBhavaNorth` after a successful library drop
- `findHouseAtPosition()`: Delegates to `ChartCoordinator.findHouseAtClientPoint()`
- `getPlanetInfo()`: Retrieve Graha data from paged structure
### Drawing Tools (assets/js/citrana-drawing-tools.js - ~3270 lines)
Comprehensive drawing system with multiple tools and editing capabilities.
Key Responsibilities:
- Implements all drawing tools (select, arrow, line, pen, laser, text, heading)
- Creates arrows via `CitranaArrow.create()` (filled `Konva.Line`, not `Konva.Arrow`)
- Delegates laser pointer to `CitranaLaser` (Canvas 2D overlay — not Konva, not serialised)
- Converts completed pen strokes to tapered `Konva.Shape` nodes (`penTaper`, `penTaperPoints`, `penTaperWidths`, `penStrokeColor`, `penBaseWidth`)
- Invisible `bounding-box-{type}` pick rects for line/arrow/pen hit-testing; pen uses `bindPenPickRectInteraction()` (click select, double-click edit, drag-after-threshold move; `beginManualPenDrag()` on touch)
- Calls `window.app.recordHistory()` for drawing and Graha edit actions (laser excluded)
- Handles shape selection and editing; `CitranaSelection` for text/heading/pen annotations (`syncPenSelectionPill()` for tapered pens)
- Provides precise positioning and hit detection (`normalizeDrawingShape()`, `repairPenPickRects()`, `raiseDrawingsAboveChart()`)
- Manages Edit UI integration (`editPenAnnotation()` — same path as Canvas Items → Edit)
- Graha text bar colour via `#text-edit-color` + `CitranaColorPicker.initGrahaBar()`
Key Features:
- Default stroke widths: Pen **4px**, Line and Arrow **4px**
- Pen: live uniform preview while drawing; on release — smoothed path (moving average + Chaikin), velocity-based width, end taper at full opacity; Select tool — click/tap select (Selection Pill), click-and-drag or touch-and-drag move, double-click/double-tap colour and stroke Edit UI
- Multi-line text/heading inline editors (**Shift+Enter** new line, **Enter** finish)
- Graha edit sessions: **Save** / click-away / Enter → `dismissPlanetEditing()` (commits if dirty); **Cancel** / Escape → `cancelPlanetEditing()` (discards)
- Control points for arrow/line with desktop hover feedback; `raiseControlPointsAbovePickRects()` keeps endpoint handles above invisible pick rects; `Adjust drawing` on handle drag end
- `bindMoveDragHistory()` for pen/text/heading reposition undo
- `syncBoundingBoxListening()` enables pick rects only in Select tool; pen pick rects get larger padding on mobile
- Auto-switch to Select Tool after Arrow, Line, Text, and Heading creation; Pen and Laser stay active for continuous drawing
- `makeShapeSelectable()` binds drag/selection once when a stroke completes (`stopDrawing`), not on every mousemove; touch double-tap guarded by `_editUiDoubleTapBound`
- Touch: `shouldPreserveTouchDrag()`, `isPenDragActive`, `getDomEventClientXY()`; `citrana-app.js` delegates touch `preventDefault` gating
- Touch detection via `CitranaDevice.isTouchDevice()`; mobile font sizing via `CitranaDevice.isMobileUA()`
Key Methods:
- `startDrawing()` / `draw()` / `stopDrawing()`
- `bindPenPickRectInteraction()` / `beginManualPenDrag()` / `editPenAnnotation()` / `shouldPreserveTouchDrag()`
- `syncBoundingBoxListening()` / `repairPenPickRects()` / `raiseControlPointsAbovePickRects()` / `raiseDrawingsAboveChart()`
- `normalizeDrawingShape()` / `findDrawingAtLayerPoint()` / `resolveDrawingHitTarget()`
- `editPlanetText()` / `editText()` / `editHeading()` — inline editors hide Edit UI first
- `startInlineContentEdit()` / `focusInlineTextarea()`: Double-click / Canvas Items **Edit text** for Text and Heading
- `createControlPoints()` / `commitControlPointAdjust()` / `bindControlPointHover()`
- `restorePersistedDrawings()` — migrates legacy `Konva.Arrow` via `CitranaArrow.fromLegacyNode()`; restores tapered pen shapes; calls `bindRestoredDrawingInteractions()`
- `bindRestoredDrawingInteractions()`: Re-bind selection/drag handlers after undo or session restore
- `showEditUIForShape()` / `setPlanetRetrogradeState()`
- `clearLaser()` / `isLaserToolAvailable()` for ephemeral laser overlay
### Citrana Device (assets/js/citrana-device.js - ~39 lines)
Shared touch, mobile UA, and viewport helpers.
Key Methods:
- `isTouchDevice()`: `'ontouchstart'` or `maxTouchPoints > 0`
- `isMobileUA()`: Mobile/tablet user-agent regex (font weight, compact layout hints)
- `isCompactViewport()`: `innerWidth <= 600` (matches mobile chart fit factor)
- `isLaserViewport()`: returns `true` on all viewports including mobile/touch
Used by `citrana-app.js`, chart templates, `citrana-drawing-tools.js`, `citrana-context-menu.js`, and `citrana-laser.js`.
### Citrana Rashis (assets/js/citrana-rashis.js - ~49 lines)
Shared Rashi names, Lucide zodiac icons (`zodiac-aries`, …), and South Indian grid numbers (1–12).
Key Exports:
- `RASHIS`: Array of `{ name, icon, number }` for all 12 signs
- `NAMES`, `NUMBERS`: Derived arrays
- `getName(rashiNumber)`, `getNumberForHouseIndex(houseIndex0to11)`, `iconHtml(icon)`
Used by `citrana-context-menu.js` (North **Set Lagna as …** submenu), chart templates, and `citrana-items-menu.js` (South Bhava row labels).
### Citrana Selection (assets/js/citrana-selection.js - ~98 lines)
**Selection Pill** — dashed outline behind selected Graha labels and Text/Heading/Pen Stroke Annotations.
Key Responsibilities:
- Dashed transparent `Konva.Rect` named `selection-pill`; `listening: false`; label text stays on top
- Pen strokes use `syncPenSelectionPill()` with bounds from `getTaperedPenBoundsInLayer()` (custom `Konva.Shape` has empty `getClientRect`)
- Extra padding on mobile (`CitranaDevice.isMobileUA()`)
Key Methods:
- `attach(labelText, parentContainer)`, `sync(labelText)`, `detach(labelText)`
Wired from South/North `selectPlanet()` / `clearSelectedPlanet()`, annotation `selectShape()` (text/heading/pen), and on Graha `dragmove`.
### Citrana Annotation Fonts (assets/js/citrana-annotation-fonts.js - ~125 lines)
Normal vs Hand-written typography for Text and Heading Annotations.
Key Responsibilities:
- **Normal:** Arial / Arial Black + `fontWeight` for bold
- **Hand-written:** self-hosted **Shantell Sans** (`assets/fonts/`; `@font-face` in `styles.css`) with `fontWeight` / `fontStyle` for bold and italic
- Legacy session support: `isHandwritten()` still recognises saved **Caveat** / **Caveat Brush** families; new annotations use Shantell Sans only
- `ensureLoaded()` preloads all four Shantell Sans faces via `document.fonts.load()`
Key Methods:
- `isHandwritten()`, `isBold()`, `isItalic()`, `setBold()`, `setItalic()`, `setMode()`, `ensureLoaded()`
Consumed by `citrana-edit-ui.js` (**Normal** / **Hand-written** buttons). Families persist in session/undo via Konva serialisation.
### Citrana Laser (assets/js/citrana-laser.js - ~248 lines)
Ephemeral laser pointer for presentations — Canvas 2D overlay above Konva stage.
Key Responsibilities:
- `.citrana-laser-canvas` DOM overlay; `pointer-events: none`; dense 1px point sampling; ~3s fade per stroke (`FADE_DURATION_MS`)
- Each mouse down/up gesture is an independent stroke array entry; `pruneStrokes()` fades and removes expired strokes in place
- Not in `serializeDrawings()`, undo/redo, or Konva PNG export
- `CitranaLaser.init(stage)` from `DrawingTools` constructor; `clear()` on `clearAll()`
Key Methods:
- `init()`, `startStroke()`, `extendStroke()`, `endStroke()`, `clear()`, `resize()`, `isAvailable()` — `isAvailable()` delegates to `CitranaDevice.isLaserViewport()`
Availability: `CitranaDevice.isLaserViewport()` returns `true` on all viewports.
### Context Menu (assets/js/citrana-context-menu.js - ~742 lines)
Provides right-click and long-press context menus for chart, bhava, and Graha interaction.
Key Responsibilities:
- Unified hit-test routing via `openContextMenuAtClientPoint()` (desktop right-click and mobile 500ms long-press)
- **`shouldBlockCanvasContextMenu()`**: Suppresses canvas menus when disabled by user, while drawing, or when active tool is not Select/Hand (reduces touch conflicts)
- **Default enablement**: `resolveDefaultCanvasContextMenuEnabled()` — on when `CitranaDevice.hasFinePointer()` (desktop mouse/trackpad); **off** on touch-primary devices until the user enables via Canvas Items
- Creates context-sensitive menus with chart-type-specific items
- **Presentation View** toggle on create chart, existing chart, and Bhava menus (`getPresentationViewMenuHtml()`)
- Prevents chart menu from overriding Graha or bhava menus
- Implements mobile-friendly touch interactions
Menu Types:
- **Chart Creation Menu** (empty canvas): Create North/South Indian chart, **Presentation View**, Clear Canvas
- **Existing Chart Menu** (canvas, no Bhava/Graha hit): **Presentation View**; Reset Chart, Reset Annotations, Clear Canvas; **North Indian only**: Set Lagna as … (Rashi flyout submenu; tap to expand on touch)
- **Bhava Menu** (bhava hit):
- **South Indian**: Header `Bhava N` (Lagna-relative); **Set as Lagna** (`set-lagna`); Clear Bhava; **Presentation View**; …
- **North Indian**: Header `Bhava N` (visual); **Set as First Bhava** (`set-first-house`); Clear Bhava; **Presentation View**; …
- **Graha Menu**: Edit Graha, Delete Graha
Presentation View (`handleAction`):
- `toggle-presentation-view` → `app.togglePresentationView()`; label toggles **Presentation View** / **Exit Presentation View**; Lucide `presentation` icon for both states
Lagna actions (`handleAction`):
- `set-lagna`: South Bhava menu → `setLagnaHouse(visualHouse)`; North chart menu → `setLagnaHouse(rashi 1–12)`. Skipped if `houseNumber` missing (no default fallback).
- `set-first-house`: North Bhava menu only → `getRashiNumberForHouse()` → `setLagnaHouse(rashi)`
Key Methods:
- `openContextMenuAtClientPoint()`, `resolveContextTarget()`, `getShapeAtClientPoint()`, `findPlanetContextById()`
- `showChartMenu()`, `showHouseMenu()`, `showPlanetMenu()`, `showExistingChartMenu()`, `showCreateChartMenu()`
- `handleAction()`, `setupMenuEventListeners()`, `setupSubmenuHover()`, `shouldBlockCanvasContextMenu()`
- `isCanvasContextMenuEnabled()` / `setCanvasContextMenuEnabled()` / `toggleCanvasContextMenu()` — persisted in `localStorage.citrana_context_menu_enabled`
- `getContextMenuItemsTitle()` / `getContextMenuItemsMeta()` / `getContextMenuToggleActionLabel()` — Canvas Items panel **Context Menu** row labels (On/Off hints)
- `openPlanetEditor()`, `removePlanetFromHouse()`, `clearHousePlanets()`, `getActiveChartTemplate()`, `findPlanetTextNode()`
### Citrana Items Menu (assets/js/citrana-items-menu.js - ~861 lines)
Floating **Canvas Items** panel for chart, Bhava, Graha, and annotation actions — primary workflow on touch; also available on desktop.
Key Responsibilities:
- Opens from `#items-menu-btn` in the zoom bar or keyboard **I** (`title="Canvas Items (I)"`)
- **Pinned chrome:** title, `#items-modal-description`, and `#items-modal-nav` Section Anchors stay fixed; only `#items-modal-body` scrolls (`scrollContainer` / `IntersectionObserver` root)
- **`#items-modal-nav`**: Section Anchors (`.items-section-nav-wrap`, `.items-section-nav-scroll-wrap`, `.items-section-chip`) — jump to Canvas, Chart, Bhavas, Grahas, Annotations, Lagna, Actions; horizontal swipe/scroll when pills overflow (mobile edge fades via `setupSectionNavScrollFades()`); `IntersectionObserver` highlights active section
- Renders sections with `id="items-section-{id}"` and `scroll-margin-top` for section jump offset
- **Canvas**: **Clear Selection**, **Context Menu** toggle (row title **Context Menu**; meta On/Off · Click/Tap to Enable/Disable; green `.items-row-context-menu-on` / red `.items-row-context-menu-off` row tint; `square-menu` / `power`/`power-off` action icons), **Graha Library** toggle (same On/Off row tint and power icons; `orbit` row icon; `toggle-graha-library` → `PlanetSystem.toggleGrahaLibrary()`), **Reset Graha Library Position** (`orbit` row icon; `move` action button; meta **Default Layout**; `reset-graha-library-position` → `PlanetSystem.resetGrahaLibraryPosition()`; closes panel; no confirmation)
- `.items-row-selected` sync with `app.getCanvasSelection()`; South Bhava rows show fixed Rashi names
- Reuses `citrana-context-menu.js` `handleAction()` for chart/Bhava/Graha actions where possible
- Text/Heading rows: **Edit text** (`startInlineContentEdit`) and **Style** (`showEditUI`); other Annotations: single **Edit** → `showEditUI`
- **Presentation View** and chart management actions mirror context menu icons
Key Methods:
- `init()`, `open()`, `close()`, `render()`, `renderSectionNav()`, `scrollToSection()`, `setupSectionNavObserver()`, `setupSectionNavScrollFades()`, `handleNavClick()`, `handleBodyClick()`, `refreshSelectionHighlight()`
- `isRowSelected()`, `renderUtilitySection()`, `getBhavaRowLabel()`
- `getDrawingShapes()`, `getAnnotationDisplayName()`, `getAnnotationIcon()`
### Citrana Session (assets/js/citrana-session.js - ~270 lines)
Save and open `.citrana.json` session files.
Key Responsibilities:
- `capture(app)` — chart data, drawings, and Options preferences (`format: citrana-session`, `version: 1`)
- `validate()` / `readFile()` — parse and validate imported JSON; `isValidFileName()` requires `.citrana.json` extension
- `normalizeDownloadFileName()` — sanitise Save As names and enforce extension
- `download(session, fileName?)` — optional user-chosen name; default `buildExportFileName()` (`citrana-session-YYYY-MM-DD-HHMMSS.citrana.json`)
- `applyOptions()` — restore indicator and Save Chart Only preferences on import
Key Methods:
- `capture()`, `validate()`, `readFile()`, `download()`, `applyOptions()`, `buildExportFileName()`, `formatTimestamp()`, `normalizeDownloadFileName()`, `isValidFileName()`
Wired from `app.saveSession()` → `showSaveAsDialog()` → `runSaveSession(fileName)` and `app.openSessionFromFile()` (after `showOpenSessionDialog()` + file picker); save/open show shared progress modal; import resets undo timeline via `history.resetToState()`.
### Citrana Arrow (assets/js/citrana-arrow.js - ~185 lines)
Unified filled-arrow geometry for correct semi-transparent rendering.
Key Responsibilities:
- Builds a closed polygon (constant-width shaft + prominent triangular head, no taper)
- Stores logical endpoints in `arrowAnchors`; outline in `Konva.Line.points()`
- Migrates saved `Konva.Arrow` nodes on chart restore
Key Methods:
- `create()`, `rebuild()`, `setAnchor()`, `setStrokeWidth()`, `fromLegacyNode()`, `isArrow()`
### Citrana Color Picker (assets/js/citrana-colorpicker.js - ~388 lines)
Centralised JSColorPicker (v1.1.0) theme and helpers.
Key Responsibilities:
- Shared `SWATCHES` (16 Apple-style rainbow colours, 2×8 grid) for Graha bar and drawing Edit UI
- Chip-only toolbar toggles (`toggleStyle: 'button'`); alpha slider; HEX format tabs hidden
- `applyToKonvaArrow()` — opaque fill + `shape.opacity()` for unified arrow transparency
- `isPickerPopupTarget()` — touch-outside dismiss must ignore `.cp_dialog` popup
Key Methods:
- `attach()`, `destroy()`, `getValue()`, `setValue()`, `createInput()`, `initGrahaBar()`
- `fromKonvaShape()`, `applyToKonvaArrow()`, `parseColorString()`, `toHex()`, `isPickerPopupTarget()`
Theme: `--cp-*` variables in `styles.css`; vendor CSS `assets/vendor/colorpicker.min.css`
### Edit UI (assets/js/citrana-edit-ui.js - ~1030 lines)
Provides context-sensitive editing controls for drawing elements.
Key Responsibilities:
- Creates floating edit interface with mobile chevron scroll (`setupEditUIScroll`, `.toolbar-scroll-*` at ≤768px)
- Provides tool-specific controls
- Manages shape property editing via `CitranaColorPicker.createInput()` colour chips
- Text/heading style via `CitranaAnnotationFonts` (**Normal** / **Hand-written**, bold, italic)
- Records one undo step per edit session on `hide()` when properties changed
- Touch-outside dismiss ignores colour picker popup (`.cp_dialog`) and Konva inline textarea (`.konva-textarea`)
Key Features:
- Session-based undo: `markEditDirty()` on changes; `_commitEditHistoryIfNeeded()` on `hide()`
- Delete sets `_skipHistoryOnHide` to avoid double steps with `Delete drawing`
- Graha retrograde button records via `setPlanetRetrogradeState()` (not session close)
- Pen defaults **4px**; tapered pen width via `penBaseWidth` attr; `getEditTarget()` / `_editSessionTarget` resolve tapered pen node (never `bounding-box-pen`) for colour and stroke updates
Key Methods:
- `show()` / `hide()`: Display/hide; commit history on hide when dirty
- `getEditTarget()`: Stable Konva node for Edit UI session (normalises pen pick rect → `drawing-pen`)
- `setupEditUIScroll()`, `createToolControls()`, `updateStrokeWidth()`, `updateStrokeColor()`, `updateTextColor()`
- `setAnnotationBold()` / `setAnnotationItalic()`: Delegate to `CitranaAnnotationFonts`
- `markEditDirty()` / `_commitEditHistoryIfNeeded()`
## Core Features
### Undo / Redo
Unified **50-step** timeline via `CitranaHistory` (`citrana-history.js`). Toolbar **Undo** / **Redo** buttons (`#undo-btn`, `#redo-btn`, Lucide `undo-2` / `redo-2`) plus **Ctrl+Z** / **Cmd+Z** undo and **Ctrl+Y** / **Ctrl+Shift+Z** / **Cmd+Shift+Z** redo. Buttons disable when `canUndo()` / `canRedo()` is false.
Snapshots chart data + `drawing-*` nodes. **Viewport:** undo/redo preserves zoom and pan (`restoreHistoryState()` restores stage scale/position after chart reload; templates pass `skipZoomToFit: true` on history restore). **Not tracked:** tool selection, Bhava highlight, UI state, chart indicator visibility preferences, Save Chart Only export preference, laser pointer strokes, **Presentation View**.
Call `window.app.recordHistory('Label')` after undoable mutations. See [ARCHITECTURE.md](ARCHITECTURE.md#undo--redo).
### Chart Types
- South Indian Chart: Traditional 4x4 square grid layout with centre empty space; Rashis are fixed per grid cell
- North Indian Chart: Diamond-shaped polygon layout with dynamic Rashi numbering based on Lagna
- **South Indian Lagna**: Right-click a bhava → **Set as Lagna** only (no chart-level Set as Lagna). Bhava menu header shows **Bhava N** counted from Lagna (not fixed grid position)
- **North Indian Lagna**: Right-click empty canvas → **Set Lagna as …** (choose Rashi); or right-click a bhava showing a Rashi → **Set as First Bhava** (that Rashi becomes Lagna). Grahas reposition by stored `rashiNumber`
- Dynamic Bhava Numbering: South Indian bhava numbers (yellow boxes) rotate from Lagna; North Indian Rashi boxes recalculate from Lagna
### Chart Display Options
- **Options modal**: `#options-btn` (gear icon in toolbar export group, after Save) opens `#options-modal`
- **Zoom Step**: **Fine (1%)** default; **Small (~10%)**, **Medium (~20%)**, **Large (~25%)** for zoom buttons, keyboard **+**/**−**, and scroll wheel (`CitranaZoom`)
- **Hide North Indian Chart Indicators**: Hides bhava numbers in black corner boxes (`tinyBoxGroupNorth`)
- **Hide South Indian Chart Indicators**: Hides lagna diagonal line, yellow bhava boxes, and black rashi boxes
- **Save Chart Only**: Same `#export-btn` exports only the chart area — fits chart, ignores zoom/pan, crops to chart bounds, transparent background, no watermark; locks `#toggle-transparency-btn` on; turning the option off restores white-background export. Falls back to full viewport when no chart
- **Persistence**: `localStorage.citrana_north_hide_indicators`, `citrana_south_hide_indicators`, `citrana_save_chart_only`, `citrana_zoom_step` (`'1'` when indicators hidden or Save Chart Only on; zoom step key removed when `fine`)
- **Undo**: Options preferences are not tracked in the undo timeline; included in `.citrana.json` session files
### Chart Management Actions
- Clear Canvas: Removes everything (charts, Grahas, drawings) and returns to blank canvas
- Reset Chart: Removes Grahas and drawings, but keeps chart structure/layout
- Reset Annotations: Removes only drawings, keeps Grahas and chart structure
### Graha Management
- 60 Major Grahas: 12 traditional Grahas on Page 1, 12 Jaimini Karakas on Page 2, 12 Tamil Grahas on Page 3, 12 Hindi Grahas on Page 4, and 12 Upagrahas and outer Grahas on Page 5
- Paging System: Five-page navigation — desktop dots and keyboard **1**–**5**; mobile swipe on dots bar with chevron hints
- Library Labels: Graha library cells show `fullName` (e.g. Page 5 Upagrahas); chart placement still uses text abbreviations
- Library Layout: No grid scrolling — desktop auto-fit columns (80×40px cells); mobile 6×2 grid with 30px cells and word-wrap for long names
- Text-based Display: Uses abbreviations on the chart instead of symbols for better compatibility
- Drag & Drop: Grahas from the floating library land in the bhava under the pointer, or in a Bhava you clicked first (`window.selectedBhavaSouth` / `window.selectedBhavaNorth` — one-shot: cleared after the next successful drop or when you click empty canvas)
- Multiple Instances: Same Graha can be placed multiple times
- Dynamic Text Sizing: Graha text scales based on Bhava occupancy
- Touch Support: Mobile-friendly touch interactions with visual feedback
- Degree Support: Add degree positions to Grahas (e.g., "Su-20")
- Graha Editing: Double-click a Graha, or right-click → **Edit Graha**, to open the floating edit panel (label, colour, retrograde)
- Graha Deletion: Right-click → **Delete Graha**, delete from the edit panel, or press **Delete** when a Graha is selected
- Retrograde Display: Underlined Graha text via Konva `textDecoration` (stored as `retrograde: boolean` on Graha data, not appended to the label)
- Legacy Migration: Older charts that used the Unicode subscript `ᵣ` are normalised automatically (marker removed, underline applied)
- 8-Character Limit: Applies to Graha label text only; retrograde does not consume a character slot
### Drawing Tools
- Select Tool: Choose and modify existing elements with Edit UI
- Arrow Tool: Unified filled arrow (`CitranaArrow`) with constant-width shaft, prominent head, and control points (default 4px)
- Line Tool: Draw straight lines and connections with control points (default 4px)
- Pen Tool: Natural taper — velocity-based width, smoothed path, end taper (default **4px** base); stored as `Konva.Shape` with `penTaper` attrs; Select tool — click/tap select, drag/touch-drag move, double-click/double-tap (or Canvas Items → Edit) for colour and stroke
- Laser Pointer: Temporary fading highlight (`CitranaLaser` Canvas overlay; shortcut **K** when available; toolbar button on mobile; not saved or undoable)
- Presentation View: Context menu or **Canvas Items** panel toggle hides toolbar, zoom bar, Graha library, Help, About, Graha edit bar, and drawing Edit UI; dismisses active edit sessions on enter. Graha Library can also be hidden independently via Canvas Items → **Graha Library** (On/Off)
- Text Tool: Multi-line editable text boxes (**Shift+Enter** new line)
- Heading Tool: Multi-line chart headings and titles
- **Hand-written Annotations:** Shantell Sans (self-hosted) via Edit UI **Normal** / **Hand-written** toggles; bold and italic via weight and style
- Undo/Redo: Unified timeline via `app.recordHistory()` — laser strokes excluded; see Undo / Redo above
- Auto-Switch Behaviour: Arrow, Line, Text, and Heading automatically switch to Select Tool after creation; Pen and Laser remain active
- Control Points: Draggable arrow/line handles with desktop hover feedback (colour invert, grab cursor)
### Control Points Feature
The control points system provides precise adjustment capabilities for arrow and line elements:
Functionality:
- Control points appear automatically when arrows or lines are selected; endpoint handles stay above invisible `bounding-box-*` pick rects (`raiseControlPointsAbovePickRects()`)
- Two draggable handles at the start and end points of each element
- Desktop hover inverts handle colours and shows grab/grabbing cursor; wider hit targets
- Real-time visual feedback during adjustment
- Per-frame synchronisation ensures control points stay attached during shape movement
- Works on both blank canvas and chart-loaded states
Technical Implementation:
- Konva.Circle objects with custom styling and event handling
- Coordinate transformation between local shape coordinates and global stage coordinates
- RequestAnimationFrame loop for continuous synchronisation
- Touch and mouse support for cross-platform compatibility
- Automatic cleanup when elements are deselected or deleted
### User Experience
- Light Theme: Clean, professional appearance with high contrast
- Responsive Design: Optimised for desktop, tablet, and mobile viewports
- Keyboard Shortcuts: Tools, undo/redo, zoom (when unlocked), delete, help — see Main Application
- Undo/Redo Toolbar: `#undo-btn` and `#redo-btn` in the top toolbar (first group); disabled when no steps available
- Context Menus: Right-click or long-press on canvas, bhava, or Graha (Select/Hand when enabled; off by default on touch-primary devices; suppressed while drawing or when disabled via Canvas Items); chart-type-specific Bhava actions; Graha edit/delete; **Presentation View**
- **Canvas hints:** Illustrated tips on a blank canvas (library, toolbar, zoom bar, Help); centred logo + start message; fade in; dismiss on first creation
- **Canvas Items**: `#items-menu-btn` or **I** — chart/Bhava/Graha/annotation actions; pinned Section Anchors; **Clear Selection**, **Context Menu**, **Graha Library** (On/Off with green/red row tint), and **Reset Graha Library Position**; row highlight syncs with canvas; **Selection Pill** for Grahas and Annotations (including pen strokes)
- **Edit UI mobile scroll**: chevron buttons and edge fades when style controls overflow on ≤768px
- Status Updates: Real-time feedback and notifications
- Ephemeral tab sessions: Refresh starts fresh — **Save Session** (`.citrana.json`) or export PNG to keep work
- Help Modal (**Guide**): `.help-intro` workspace overview; sections aligned with README (Charts → Privacy Note); portable `.citrana.json` session guidance; keyboard shortcuts
- About Modal: Information about Citrana with creator details and links
- Welcome Modal: First visit only; `#welcome-modal-backdrop` + dialog; `body.welcome-modal-open` dims app chrome behind grey backdrop (beats Safari mobile toolbar/zoom z-index overrides); peripheral canvas hints visible between backdrop and dialog; **Creating Your First Chart** (6 steps — chart type, Lagna, Grahas, library pages **1**–**5**, Annotations, Save Session / PNG); mobile **Canvas Items** via layers icon (not **I**); loading bar stages in title case ending with **Ready!** at 100%; `closeWelcomeModal()` + `localStorage`; backdrop click or close button; early close stops timer; mobile compact, no scroll
- Options Modal: **Zoom Step**, chart indicator toggles, and **Save Chart Only** export; shared modal width with Help
- Confirmation Modal (`#confirmation-modal`): Reused for destructive confirm (`confirmation-modal--confirm`), single-action notices (`--alert`), **Open Session** intro (`--open`), and **Save As** (`--save-as` with `#confirmation-filename-input`); dynamic message + optional warning in `aria-describedby`
- Export Progress Modal: Shared `#export-progress-modal` for export PNG, save session, and open session; non-dismissible; dynamic title; live status in `aria-describedby`; `aria-busy` while operation runs
- Modal accessibility: Escape dismiss, Tab focus trap, focus restore on close (see Main Application)
- Zoom Controls: `#zoom-in`, `#zoom-out`, `#reset-zoom`, `#zoom-lock` (default locked), `#zoom-level`, **`#items-menu-btn`**
- Zoom Lock: Prevents accidental scroll-wheel and +/- zoom until user unlocks; reset zoom always available
- Toolbar scroll: `#toolbar-scroll-viewport` with chevrons when tools overflow on narrow screens
### Export & Sharing
- **Full viewport export** (default): entire stage capture; 100px padding and watermark; respects zoom/pan and transparency toggle; **Save As** dialog then progress dialog **Exporting Chart**
- **Save Chart Only**: chart-area crop via `getExportCropRect()` after temporary `zoomToFit()`; transparent, no padding or watermark; restores zoom/pan after capture
- **Save Session**: **Save As** dialog (default `citrana-session-{timestamp}.citrana.json`), then `.citrana.json` download with chart, Grahas, Annotations, and Options (including Zoom Step); progress dialog during save; save to cloud storage to resume on another device
- **Open Session**: **Open Session** intro dialog → file picker (`.citrana.json` only) → confirmation when replacing existing work → progress dialog during import; invalid files show in-app notice
- High-Resolution PNG: `pixelRatio: 2` for both export modes
- Cross-Platform: Works on all modern browsers
- GitHub Pages Compatible: No build process required
## Development Guidelines
### Code Style
- Use ES6+ JavaScript features
- Follow existing naming conventions
- Add comprehensive comments for new features
- Maintain modular architecture
- Use custom CSS classes for styling
### Debug logging
- Use `citranaDebug(...)` from `citrana-debug.js` for contributor trace logs (enabled by default)
- Silence in DevTools: `localStorage.setItem('citrana_debug', '0')` then refresh; remove the key to re-enable
- Use `console.error` for real failures only
### File Headers
All JavaScript and CSS files use a standardised comment header format:
```javascript
/**
* filename.extension
* Citrana • https://github.com/IAmVigneswaran/Soothsayer-Citrana
* © 2026 Vigneswaran Rajkumar • Licensed under MIT License
* One line description of the file
*/
```
### File Organisation
- Keep all assets in the assets/ directory
- JavaScript files in assets/js/
- CSS files in assets/css/
- Images in assets/images/
- SVGs in assets/svgs/
- Favicons in assets/favicon/
### Browser Compatibility
- Desktop: Brave 1.80+, Chrome 138+, Firefox 128+, Safari 18+, Edge 138+
- Note: For Brave browser, disable Brave Shields for optimal functionality
- Features: Canvas API, localStorage, ES6+ JavaScript (classic script tags), Touch Events
Mobile and touch:
- Desktop is the primary supported experience
- Mobile/touch layouts are tuned (safe areas, compact Graha library, toolbar scroll, drawing tools in toolbar)
- Use the **Canvas Items** panel (`#items-menu-btn` or **I**) for chart/Bhava/Graha/annotation actions when context menus are awkward on touch
- Laser pointer available on all viewports (`CitranaDevice.isLaserViewport()`)
### Performance Considerations
- Efficient canvas rendering with Konva.js
- Optimised resize handlers
- Optimised Graha placement algorithms
- Minimal DOM manipulation
- Touch event optimisation for desktop performance
## Customisation Guidelines
### Adding New Grahas
Edit the Graha data objects in assets/js/citrana-planet-system.js:
```javascript
// Graha data - Page 1 (Traditional Grahas)
this.planetsPage1 = {
'Lg': {
name: 'Lagna',
fullName: 'Lagna',
color: '#000000'
},
'Su': {
name: 'Sun',
fullName: 'Sun',
color: '#e2792e'
},
'Mo': {
name: 'Moon',
fullName: 'Moon',
color: '#868484'
},
'Me': {
name: 'Mercury',
fullName: 'Mercury',
color: '#08b130'
},
'Ve': {
name: 'Venus',
fullName: 'Venus',
color: '#eb539f'
},
'Ma': {
name: 'Mars',
fullName: 'Mars',
color: '#da3b26'
},
'Ju': {
name: 'Jupiter',
fullName: 'Jupiter',
color: '#ffa200'
},
'Sa': {
name: 'Saturn',
fullName: 'Saturn',
color: '#3274b5'
},
'Ra': {
name: 'Rahu',
fullName: 'Rahu',
color: '#4c4b4b'
},
'Ke': {
name: 'Ketu',
fullName: 'Ketu',
color: '#4c4b4b'
},