@@ -34,6 +34,30 @@ const TOTALITY_MOON_COLOR = TOTALITY_SKY_COLOR;
3434const TOTALITY_MOON_BORDER_COLOR = "#1f2c47" ;
3535const TOTALITY_HORIZON_LINE_COLOR = "rgba(255, 174, 205, 0.75)" ;
3636const TOTALITY_HORIZON_GLOW_COLOR = "rgba(255, 136, 182, 0.42)" ;
37+ const LANDSCAPE_HORIZONTAL_FOV_DEG_24MM = 74 ;
38+ const COMPASS_MARKERS = [
39+ { label : "N" , azimuthDeg : 0 } ,
40+ { label : "NNE" , azimuthDeg : 22.5 } ,
41+ { label : "NE" , azimuthDeg : 45 } ,
42+ { label : "ENE" , azimuthDeg : 67.5 } ,
43+ { label : "E" , azimuthDeg : 90 } ,
44+ { label : "ESE" , azimuthDeg : 112.5 } ,
45+ { label : "SE" , azimuthDeg : 135 } ,
46+ { label : "SSE" , azimuthDeg : 157.5 } ,
47+ { label : "S" , azimuthDeg : 180 } ,
48+ { label : "SSW" , azimuthDeg : 202.5 } ,
49+ { label : "SW" , azimuthDeg : 225 } ,
50+ { label : "WSW" , azimuthDeg : 247.5 } ,
51+ { label : "W" , azimuthDeg : 270 } ,
52+ { label : "WNW" , azimuthDeg : 292.5 } ,
53+ { label : "NW" , azimuthDeg : 315 } ,
54+ { label : "NNW" , azimuthDeg : 337.5 } ,
55+ ] as const ;
56+
57+ function normalizeSignedDeltaDeg ( fromDeg : number , toDeg : number ) {
58+ const delta = ( ( toDeg - fromDeg + 540 ) % 360 ) - 180 ;
59+ return delta === - 180 ? 180 : delta ;
60+ }
3761
3862export type PhotographyGuidePayload = {
3963 eclipseId : string ;
@@ -89,6 +113,7 @@ export default function PhotographyGuideScreen({
89113 const [ totalPictures , setTotalPictures ] = useState < PhotographyGuidePictureCount > ( 5 ) ;
90114 const [ isCountPickerOpen , setIsCountPickerOpen ] = useState ( false ) ;
91115 const [ isLandscapeCompositeOpen , setIsLandscapeCompositeOpen ] = useState ( false ) ;
116+ const [ showCompositeMarkings , setShowCompositeMarkings ] = useState ( true ) ;
92117 const [ compositeStageSize , setCompositeStageSize ] = useState ( {
93118 width : 0 ,
94119 height : 0 ,
@@ -184,6 +209,22 @@ export default function PhotographyGuideScreen({
184209 if ( typeof activeCompositeHorizonY !== "number" ) return undefined ;
185210 return Math . max ( 0 , compositeStageSize . height - activeCompositeHorizonY ) ;
186211 } , [ activeCompositeHorizonY , compositeStageSize . height ] ) ;
212+ const horizonCompassMarkers = useMemo ( ( ) => {
213+ if ( ! compositeLayout ) return [ ] ;
214+ const maxPlacement = compositeLayout . placements . find (
215+ ( placement ) => placement . phaseBucket === "MAX" && typeof placement . sunAzimuthDeg === "number" ,
216+ ) ;
217+ if ( ! maxPlacement || typeof maxPlacement . sunAzimuthDeg !== "number" ) return [ ] ;
218+ const centerAzimuthDeg = maxPlacement . sunAzimuthDeg ;
219+
220+ return COMPASS_MARKERS . map ( ( marker ) => {
221+ const deltaDeg = normalizeSignedDeltaDeg ( centerAzimuthDeg , marker . azimuthDeg ) ;
222+ const x =
223+ compositeLayout . anchorX +
224+ ( deltaDeg / LANDSCAPE_HORIZONTAL_FOV_DEG_24MM ) * compositeStageSize . width ;
225+ return { ...marker , x, inFrame : x >= 0 && x <= compositeStageSize . width } ;
226+ } ) . filter ( ( marker ) => marker . inFrame ) ;
227+ } , [ compositeLayout , compositeStageSize . width ] ) ;
187228 const handleCompositeStageLayout = ( event : LayoutChangeEvent ) => {
188229 const nextWidth = Math . round ( event . nativeEvent . layout . width ) ;
189230 const nextHeight = Math . round ( event . nativeEvent . layout . height ) ;
@@ -363,6 +404,16 @@ export default function PhotographyGuideScreen({
363404 />
364405 < View style = { styles . compositeModal } >
365406 < Text style = { styles . compositeModalTitle } > Landscape composite</ Text >
407+ < Pressable
408+ style = { styles . compositeMarkingsToggleBtn }
409+ onPress = { ( ) => setShowCompositeMarkings ( ( current ) => ! current ) }
410+ accessibilityRole = "button"
411+ accessibilityLabel = "Toggle composite markings"
412+ >
413+ < Text style = { styles . compositeMarkingsToggleText } >
414+ { showCompositeMarkings ? "Hide" : "Show" } markings, directions, and shot numbers
415+ </ Text >
416+ </ Pressable >
366417 < View style = { styles . compositeFrame } onLayout = { handleCompositeStageLayout } >
367418 < View
368419 style = { [
@@ -402,6 +453,26 @@ export default function PhotographyGuideScreen({
402453 : null ,
403454 ] }
404455 />
456+ { showCompositeMarkings && typeof activeCompositeHorizonY === "number"
457+ ? horizonCompassMarkers . map ( ( marker ) => (
458+ < View key = { `horizon-marker-${ marker . label } ` } style = { { left : marker . x - 10 } } >
459+ < View
460+ style = { [
461+ styles . compositeDirectionTick ,
462+ { top : activeCompositeHorizonY - 6 } ,
463+ ] }
464+ />
465+ < Text
466+ style = { [
467+ styles . compositeDirectionLabel ,
468+ { top : activeCompositeHorizonY + 4 } ,
469+ ] }
470+ >
471+ { marker . label }
472+ </ Text >
473+ </ View >
474+ ) )
475+ : null }
405476 { compositeLayout ? (
406477 < >
407478 < View
@@ -415,92 +486,98 @@ export default function PhotographyGuideScreen({
415486 />
416487 { compositeLayout . placements . map ( ( placement ) => (
417488 < View key = { placement . index } >
418- { isTotalCompositeTheme &&
419- placement . phaseBucket === "MAX" &&
420- placement . showMoon &&
421- placement . moon ? (
489+ { ! placement . isAboveHorizon ? null : (
422490 < >
491+ { isTotalCompositeTheme &&
492+ placement . phaseBucket === "MAX" &&
493+ placement . showMoon &&
494+ placement . moon ? (
495+ < >
496+ < View
497+ style = { [
498+ styles . compositeCoronaGlow ,
499+ {
500+ width : Math . max ( placement . sunRadius * 9 , 16 ) ,
501+ height : Math . max ( placement . sunRadius * 9 , 16 ) ,
502+ borderRadius : Math . max ( placement . sunRadius * 4.5 , 8 ) ,
503+ left : placement . x - Math . max ( placement . sunRadius * 4.5 , 8 ) ,
504+ top : placement . y - Math . max ( placement . sunRadius * 4.5 , 8 ) ,
505+ } ,
506+ ] }
507+ />
508+ < View
509+ style = { [
510+ styles . compositeCoronaRing ,
511+ {
512+ width : Math . max ( placement . sunRadius * 6 , 10 ) ,
513+ height : Math . max ( placement . sunRadius * 6 , 10 ) ,
514+ borderRadius : Math . max ( placement . sunRadius * 3 , 5 ) ,
515+ left : placement . x - Math . max ( placement . sunRadius * 3 , 5 ) ,
516+ top : placement . y - Math . max ( placement . sunRadius * 3 , 5 ) ,
517+ } ,
518+ ] }
519+ />
520+ </ >
521+ ) : null }
423522 < View
424523 style = { [
425- styles . compositeCoronaGlow ,
524+ styles . compositeSun ,
426525 {
427- width : Math . max ( placement . sunRadius * 9 , 16 ) ,
428- height : Math . max ( placement . sunRadius * 9 , 16 ) ,
429- borderRadius : Math . max ( placement . sunRadius * 4.5 , 8 ) ,
430- left : placement . x - Math . max ( placement . sunRadius * 4.5 , 8 ) ,
431- top : placement . y - Math . max ( placement . sunRadius * 4.5 , 8 ) ,
432- } ,
433- ] }
434- />
435- < View
436- style = { [
437- styles . compositeCoronaRing ,
438- {
439- width : Math . max ( placement . sunRadius * 6 , 10 ) ,
440- height : Math . max ( placement . sunRadius * 6 , 10 ) ,
441- borderRadius : Math . max ( placement . sunRadius * 3 , 5 ) ,
442- left : placement . x - Math . max ( placement . sunRadius * 3 , 5 ) ,
443- top : placement . y - Math . max ( placement . sunRadius * 3 , 5 ) ,
526+ width : placement . sunRadius * 2 ,
527+ height : placement . sunRadius * 2 ,
528+ borderRadius : placement . sunRadius ,
529+ left : placement . x - placement . sunRadius ,
530+ top : placement . y - placement . sunRadius ,
444531 } ,
445532 ] }
446533 />
534+ { placement . showMoon && placement . moon ? (
535+ < View
536+ style = { [
537+ styles . compositeMoon ,
538+ isTotalCompositeTheme
539+ ? {
540+ backgroundColor : TOTALITY_MOON_COLOR ,
541+ borderColor : TOTALITY_MOON_BORDER_COLOR ,
542+ }
543+ : null ,
544+ {
545+ width : placement . moon . radius * 2 ,
546+ height : placement . moon . radius * 2 ,
547+ borderRadius : placement . moon . radius ,
548+ left : placement . moon . x - placement . moon . radius ,
549+ top : placement . moon . y - placement . moon . radius ,
550+ } ,
551+ ] }
552+ />
553+ ) : null }
554+ { showCompositeMarkings ? (
555+ < View
556+ style = { [
557+ styles . compositeShotIndexTag ,
558+ {
559+ left : placement . x - 9 ,
560+ top : placement . y + placement . sunRadius + 3 ,
561+ } ,
562+ placement . clamped ? styles . compositeShotIndexTagClamped : null ,
563+ ] }
564+ >
565+ < Text style = { styles . compositeShotIndexText } > { placement . index } </ Text >
566+ </ View >
567+ ) : null }
568+ { showCompositeMarkings && placement . clamped ? (
569+ < View
570+ style = { [
571+ styles . compositeClampIndicator ,
572+ {
573+ left : placement . x + placement . sunRadius - 4 ,
574+ top : placement . y - placement . sunRadius - 4 ,
575+ } ,
576+ ] }
577+ />
578+ ) : null }
447579 </ >
448- ) : null }
449- < View
450- style = { [
451- styles . compositeSun ,
452- {
453- width : placement . sunRadius * 2 ,
454- height : placement . sunRadius * 2 ,
455- borderRadius : placement . sunRadius ,
456- left : placement . x - placement . sunRadius ,
457- top : placement . y - placement . sunRadius ,
458- } ,
459- ] }
460- />
461- { placement . showMoon && placement . moon ? (
462- < View
463- style = { [
464- styles . compositeMoon ,
465- isTotalCompositeTheme
466- ? {
467- backgroundColor : TOTALITY_MOON_COLOR ,
468- borderColor : TOTALITY_MOON_BORDER_COLOR ,
469- }
470- : null ,
471- {
472- width : placement . moon . radius * 2 ,
473- height : placement . moon . radius * 2 ,
474- borderRadius : placement . moon . radius ,
475- left : placement . moon . x - placement . moon . radius ,
476- top : placement . moon . y - placement . moon . radius ,
477- } ,
478- ] }
479- />
480- ) : null }
481- < View
482- style = { [
483- styles . compositeShotIndexTag ,
484- {
485- left : placement . x - 9 ,
486- top : placement . y + placement . sunRadius + 3 ,
487- } ,
488- placement . clamped ? styles . compositeShotIndexTagClamped : null ,
489- ] }
490- >
491- < Text style = { styles . compositeShotIndexText } > { placement . index } </ Text >
492- </ View >
493- { placement . clamped ? (
494- < View
495- style = { [
496- styles . compositeClampIndicator ,
497- {
498- left : placement . x + placement . sunRadius - 4 ,
499- top : placement . y - placement . sunRadius - 4 ,
500- } ,
501- ] }
502- />
503- ) : null }
580+ ) }
504581 </ View >
505582 ) ) }
506583 </ >
@@ -510,7 +587,8 @@ export default function PhotographyGuideScreen({
510587 24mm framing simulation with MAX anchored at frame center.
511588 </ Text >
512589 < Text style = { styles . compositeLegendText } >
513- Numbers are shot indices. Amber dots mark edge-clamped shots.
590+ Numbers are shot indices. Horizon ticks show compass directions. Amber dots mark
591+ edge-clamped shots.
514592 </ Text >
515593 < Pressable
516594 style = { styles . compositeModalCloseBtn }
@@ -903,6 +981,36 @@ function createStyles(colors: ReturnType<typeof useAppTheme>["colors"]) {
903981 borderColor : "#7a5222" ,
904982 opacity : 0.9 ,
905983 } ,
984+ compositeDirectionTick : {
985+ position : "absolute" ,
986+ width : 1 ,
987+ height : 12 ,
988+ backgroundColor : "rgba(255,255,255,0.7)" ,
989+ } ,
990+ compositeDirectionLabel : {
991+ position : "absolute" ,
992+ width : 20 ,
993+ textAlign : "center" ,
994+ color : "#ffffff" ,
995+ fontSize : 9 ,
996+ fontWeight : "700" ,
997+ textShadowColor : "rgba(0,0,0,0.4)" ,
998+ textShadowRadius : 2 ,
999+ textShadowOffset : { width : 0 , height : 1 } ,
1000+ } ,
1001+ compositeMarkingsToggleBtn : {
1002+ borderRadius : 10 ,
1003+ borderWidth : 1 ,
1004+ borderColor : colors . inputBorder ,
1005+ backgroundColor : colors . surfaceMuted ,
1006+ paddingVertical : 8 ,
1007+ paddingHorizontal : 10 ,
1008+ } ,
1009+ compositeMarkingsToggleText : {
1010+ color : colors . textSecondary ,
1011+ fontSize : 12 ,
1012+ fontWeight : "700" ,
1013+ } ,
9061014 compositeModal : {
9071015 width : "100%" ,
9081016 borderRadius : 12 ,
0 commit comments