# Project export: Smart Business Card

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 12.0
- Tagline: AR-based business card app using Unity and Vuforia displays 3D info, offers virtual tours, and profession-specific features, enhancing interactivity and accessibility beyond traditional cards.
- Devpost: https://devpost.com/software/smart-business-card
- GitHub: https://github.com/rajat343/smart_business_card
- Video: https://www.youtube.com/embed/IJ85dCd2?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — rajat343 (1 commits)

## Devpost submission (written by the team)

### Inspiration

Traditional business cards contain limited information with small fonts, making them inconvenient, especially for people with vision issues. We wanted to enhance them using AR to create a more interactive and informative experience.

### What it does

The Smart Business Card uses AR (Augmented Reality) through Unity and Vuforia to display rich, 3D, and interactive information when scanned with a mobile device. It can show details like contact info, 3D models, or even virtual tours depending on the profession.

### How we built it

We built the app in Unity, integrating Vuforia SDK for image recognition and marker tracking. The physical business card acts as the marker, triggering 3D visualizations or information overlays on the mobile screen.

### Challenges we ran into

Setting up accurate marker tracking and stable AR overlays. Optimizing 3D rendering for mobile devices. Managing data retrieval and integration of profession-specific features.

### Accomplishments we're proud of

Successfully turning a traditional business card into an interactive AR experience. Enabling dynamic, profession-based extensions like virtual tours for builders or 3D product previews for showroom owners.

### What we learned

We learned how to integrate Vuforia with Unity, perform image recognition, manage AR content placement, and optimize mobile AR performance for smooth user experiences.

### What's next

We plan to add VR integration, cloud-based data storage, and customizable templates so professionals can personalize their AR experiences and update business card info dynamically.

## README (from the GitHub repository)

# Smart Business Card

A Unity-based Augmented Reality (AR) application designed to enhance the functionality of 
traditional business cards. Using image recognition and AR technology, this project provides 
a more interactive and accessible way to access and expand on information from business cards.

## Table of Contents

- Introduction
- Features
- Technology Stack
- Installation
- Usage
- Potential Applications
- Future Enhancements
- Contributing
- License


## Introduction

Traditional business cards are limited by size, lack of interactivity, and accessibility 
challenges, especially for people with vision impairments. This project aims to modernize 
the business card experience by integrating AR technology. By scanning a business card with 
a mobile device, users can access extended information and explore interactive 3D content, 
creating a richer and more accessible experience.

The application uses Unity and Vuforia for image recognition and AR rendering, enabling 
users to view additional business information and interact with virtual objects associated 
with the card.


## Features

- **Enhanced Information Access**: Unlock additional information beyond the static text 
  on a business card.
- **Virtual 3D Map**: View location-based information and maps through AR directly on the 
  business card.
- **Industry-Specific Functionalities**:
  - Builders can showcase virtual tours of buildings or construction sites.
  - Showroom owners can provide interactive 3D views of products, allowing customers to 
    explore options like color and features.
- **AR Marker Recognition**: Business cards serve as AR markers, enabling easy and accurate 
  information retrieval.
- **Unity and Vuforia Integration**: The application is built on Unity and uses the Vuforia 
  library for image recognition and AR tracking.


## Technology Stack

- **Unity**: Game engine used for creating and rendering the AR experience.
- **Vuforia**: AR platform for marker-based tracking and image recognition.
- **C#**: Primary programming language for scripting in Unity.
- **JavaScript**: Used for additional scripting.
- **Image Processing**: Processes business card images for AR marker recognition and 
  content augmentation.



## Installation

### Prerequisites

- **Unity**: Download and install [Unity Hub](https://unity.com/download) and select a 
  compatible Unity version (unity version 6 or above).
- **Vuforia SDK**: Install the Vuforia Engine from the Unity Asset Store or through the 
  Vuforia Developer Portal.


### Steps

```markdown
1. Clone the Repository:
   git clone https://github.com/rajat343/ar_business_card.git
   cd ar_business_card


2. Open in Unity:
   - Open Unity Hub.
   - Select "Add Project" and navigate to the `smart_business_card` folder to open the project in Unity.


3. Setup Vuforia:
   - Register on the [Vuforia Developer Portal](https://developer.vuforia.com/) and obtain a license key.
   - In Unity, go to `Window` > `Vuforia Configuration` and enter your license key.


4. Build Settings:
   - Open `File` > `Build Settings`.
   - Select your target platform (e.g., Android or iOS).
   - Ensure that Vuforia Augmented Reality Support is enabled under `Player Settings`.


5. Run the Project:
   - Connect your mobile device or use an emulator.
   - Click on the "Play" button in Unity to test the app, or build and deploy it to a device for a full experience.





## Detected evidence (automated analysis)

Indexed codebase: 15 recognized source files, 48 KB.
- C# (language) — detected in the code

## Codebase structure (from repository index)

### Files (18 of 18)

```
.gitignore
Assets/Scripts/_ARButton.cs
Assets/Scripts/_BusinessCardButtonManager.cs
Assets/Scripts/_TrackableVisibility.cs
Assets/Scripts/_VideoPlaneController.cs
Assets/Scripts/BuilderBackScript.cs
Assets/Scripts/BuilderFrontScript.cs
Assets/Scripts/CarBackScript.cs
Assets/Scripts/CarFrontScript.cs
Assets/Scripts/EnableCollidersOnTrack.cs
Assets/Scripts/LamboDoorBehaviour.cs
Assets/Scripts/VRLookWalk.cs
Assets/Scripts/XRDiagnosticLogger.cs
Assets/Scripts/XRInitializer.cs
Assets/Scripts/XRSceneLoader.cs
Packages/manifest.json
Packages/packages-lock.json
README.md
```

### Dependencies

No dependency index available.

### Recent commits (newest first)

- README added
- some script changes
- Changes to scripts
- commit

## Key source files (fetched from GitHub, selected and truncated for size)

### Assets/Scripts/XRInitializer.cs

```c#
using UnityEngine;
using System.Collections;
using UnityEngine.XR.Management;

public class XRInitializer : MonoBehaviour
{
    IEnumerator Start()
    {
        Debug.Log("[XRInitializer] Begin manual XR initialization...");
        var xrManager = XRGeneralSettings.Instance.Manager;

        if (xrManager == null)
        {
            Debug.LogError("[XRInitializer] XR Manager not found.");
            yield break;
        }

        // Initialize loader synchronously (or InitializeLoader() coroutine)
        yield return xrManager.InitializeLoader();

        if (xrManager.activeLoader == null)
        {
            Debug.LogError("[XRInitializer] Failed to initialize XR loader.");
            yield break;
        }

        // Start XR subsystems (display/input)
        xrManager.StartSubsystems();
        Debug.Log("[XRInitializer] XR subsystems started.");
    }
}

```

### Assets/Scripts/_ARButton.cs

```c#
using UnityEngine;

// Button action types for AR interactions
public enum ButtonAction 
{ 
    Email, 
    Phone, 
    Map, 
    Scene, 
    Video 
}

/// <summary>
/// ARButton component for handling various AR button interactions
/// </summary>
public class ARButton : MonoBehaviour
{
    // The type of action this button will perform
    public ButtonAction action = ButtonAction.Email;
    
    [Tooltip("email / phone / url / sceneName depending on action")]
    public string actionData;
    
    [Tooltip("Only for Video action: assign the video plane GameObject here")]
    public GameObject targetObject;

    // Placeholder method for future initialization logic
    private void Start()
    {
        // TODO: Add initialization code here if needed
    }

    // Placeholder method for future update logic
    private void Update()
    {
        // TODO: Add per-frame logic here if needed
    }
}
```

### Assets/Scripts/XRSceneLoader.cs

```c#
using System.Collections;
using UnityEngine;
using UnityEngine.XR.Management;

public class XRSceneLoader : MonoBehaviour
{
    IEnumerator Start()
    {
        Debug.Log("[XRSceneLoader] Starting XR initialization for Cardboard...");

        // Small delay to let AR unload completely
        yield return new WaitForSeconds(0.5f);

        // Initialize XR loader
        yield return XRGeneralSettings.Instance.Manager.InitializeLoader();

        if (XRGeneralSettings.Instance.Manager.activeLoader == null)
        {
            Debug.LogError("[XRSceneLoader] ❌ Failed to initialize XR loader!");
        }
        else
        {
            XRGeneralSettings.Instance.Manager.StartSubsystems();
            Debug.Log("[XRSceneLoader] ✅ XR started successfully (Cardboard).");
        }
    }

    private void OnDestroy()
    {
        Debug.Log("[XRSceneLoader] Stopping XR before scene unload...");
        XRGeneralSettings.Instance.Manager.StopSubsystems();
        XRGeneralSettings.Instance.Manager.DeinitializeLoader();
    }
}

```

### Assets/Scripts/XRDiagnosticLogger.cs

```c#
using UnityEngine;
using UnityEngine.XR.Management;
using System.Linq;
using System.Collections.Generic;

/// <summary>
/// Diagnostic utility for logging XR loader information
/// Helps debug XR initialization and loader configuration issues
/// </summary>
public class XRDiagnosticLogger : MonoBehaviour
{
    // Log prefix for easy filtering in console
    private const string LOG_PREFIX = "[XRDiagnosticLogger]";
    
    // Separator for loader names in output
    private const string LOADER_NAME_SEPARATOR = ", ";

    /// <summary>
    /// Initialize and log XR loader diagnostic information
    /// </summary>
    void Start()
    {
        LogRegisteredLoaders();
        LogActiveLoader();
    }

    /// <summary>
    /// Log all registered XR loaders in the system
    /// </summary>
    private void LogRegisteredLoaders()
    {
        // Get the XR manager instance
        XRManagerSettings xrManager = XRGeneralSettings.Instance.Manager;
        
        // Retrieve the list of registered loaders
        List<XRLoader> registeredLoaders = xrManager.loaders;
        
        // Extract loader names
        IEnumerable<string> loaderNames = registeredLoaders.Select(loader => loader.name);
        
        // Join names into a comma-separated string
        string loaderList = string.Join(LOADER_NAME_SEPARATOR, loaderNames);
        
        // Log the registered loaders
        Debug.Log(LOG_PREFIX + " Registered Loaders: " + loaderList);
    }

    /// <summary>
    /// Log the currently active XR loader if one exists
    /// </summary>
    private void LogActiveLoader()
    {
        // Get the XR manager instance
        XRManagerSettings xrManager = XRGeneralSettings.Instance.Manager;
        
        // Get the active loader reference
        XRLoader activeXRLoader = xrManager.activeLoader;
        
        // Check if there is an active loader
        if (activeXRLoader != null)
        {
            // Log the active loader name
            string activeLoaderName = activeXRLoader.name;
            Debug.Log(LOG_PREFIX + " Active Loader: " + activeLoaderName);
        }
        else
        {
            // Warn that no loader is currently active
            Debug.LogWarning(LOG_PREFIX + " No active loader currently active.");
        }
    }
}
```

### Assets/Scripts/EnableCollidersOnTrack.cs

```c#
using UnityEngine;
using Vuforia;

/// <summary>
/// Enables or disables colliders on child objects based on tracking status
/// Automatically toggles colliders when Vuforia target is tracked or lost
/// </summary>
public class EnableCollidersOnTrack : MonoBehaviour
{
    // Reference to the Vuforia observer behaviour component
    private ObserverBehaviour targetObserver;
    
    // Cache for child colliders to avoid repeated GetComponentsInChildren calls
    private Collider[] cachedColliders;

    /// <summary>
    /// Initialize observer and cache colliders
    /// </summary>
    void Awake()
    {
        // Get the observer component attached to this GameObject
        targetObserver = GetComponent<ObserverBehaviour>();
        
        if (targetObserver != null)
        {
            // Subscribe to tracking status changes
            targetObserver.OnTargetStatusChanged += OnTargetStatusChanged;
        }
        
        // Cache all child colliders for better performance
        cachedColliders = GetComponentsInChildren<Collider>(true);
    }

    /// <summary>
    /// Clean up event subscriptions on destroy
    /// </summary>
    private void OnDestroy()
    {
        if (targetObserver != null)
        {
            // Unsubscribe from tracking events to prevent memory leaks
            targetObserver.OnTargetStatusChanged -= OnTargetStatusChanged;
        }
    }

    /// <summary>
    /// Handle changes in target tracking status
    /// </summary>
    /// <param name="behaviour">The observer behaviour that triggered the event</param>
    /// <param name="status">The current tracking status</param>
    private void OnTargetStatusChanged(ObserverBehaviour behaviour, TargetStatus status)
    {
        // Determine if the target is currently being tracked
        bool shouldEnableColliders = status.Status == Status.TRACKED || status.Status == Status.EXTENDED_TRACKED;

        // Toggle all child colliders based on tracking status
        int colliderCount = cachedColliders.Length;
        for (int i = 0; i < colliderCount; i++)
        {
            if (cachedColliders[i] != null)
            {
                cachedColliders[i].enabled = shouldEnableColliders;
            }
        }

        // Log the state change for debugging purposes
        string stateMessage = shouldEnableColliders ? "enabled" : "disabled";
        Debug.Log("[EnableCollidersOnTrack] Colliders " + stateMessage + " for " + gameObject.name);
    }
}
```

### Assets/Scripts/CarBackScript.cs

```c#
using UnityEngine;
using UnityEngine.SceneManagement;

/// <summary>
/// Handles back button interaction for the car scene
/// Uses raycast detection to load the previous scene
/// </summary>
public class CarBackScript : MonoBehaviour
{
    // Reference to the main AR camera
    private Camera arCamera;
    
    // Name of the button object to detect
    private const string BACK_BUTTON_NAME = "car_back_btn";
    
    // Name of the scene to load when back button is pressed
    private const string TARGET_SCENE_NAME = "CarScene";

    /// <summary>
    /// Initialize camera reference and log status
    /// </summary>
    void Start()
    {
        // Ensure AR Camera has the "MainCamera" tag
        arCamera = Camera.main;
        
        // Log initialization status for debugging
        Debug.Log("[CarBackScript] Initialized with AR Camera: " + (arCamera != null));
    }

    /// <summary>
    /// Check for user input each frame
    /// </summary>
    void Update()
    {
        // Handle touch input (on mobile devices)
        if (Input.touchCount > 0 && Input.touches[0].phase == TouchPhase.Began)
        {
            HandleRaycast(Input.touches[0].position);
        }

        // Handle mouse click (for Unity Editor testing)
        if (Input.GetMouseButtonDown(0))
        {
            HandleRaycast(Input.mousePosition);
        }
    }

    /// <summary>
    /// Perform raycast from screen position and check for back button hit
    /// </summary>
    /// <param name="screenPosition">Screen position of the input</param>
    private void HandleRaycast(Vector3 screenPosition)
    {
        // Create ray from camera through screen position
        Ray ray = arCamera.ScreenPointToRay(screenPosition);
        RaycastHit hit;

        // Cast ray and check for collision
        if (Physics.Raycast(ray, out hit))
        {
            // Log what object was hit
            Debug.Log("[CarBackScript] Raycast hit: " + hit.collider.gameObject.name);

            // Check if the hit object is the back button
            if (hit.collider.gameObject.name == BACK_BUTTON_NAME)
            {
                // Log scene transition
                Debug.Log("[CarBackScript] Back button pressed → loading scene: " + TARGET_SCENE_NAME);
                
                // Load the target scene by name
                SceneManager.LoadScene(TARGET_SCENE_NAME);
            }
            else
            {
                // Log that a different object was hit
                Debug.Log("[CarBackScript] Hit object is not the back button.");
            }
        }
        else
        {
            // Log that raycast didn't hit anything
            Debug.Log("[CarBackScript] Raycast hit nothing");
        }
    }
}
```

### Assets/Scripts/LamboDoorBehaviour.cs

```c#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// Controls the opening and closing animation of Lamborghini-style doors
/// Doors open when camera enters trigger zone and close when it exits
/// </summary>
public class LamboDoorBehaviour : MonoBehaviour 
{
    // Current rotation angle of the door
    private float currentRotationAngle = 0f;
    
    // Target rotation angle the door is moving towards
    private float targetRotationAngle = 0f;
    
    // Speed multiplier for door animation
    private const float DOOR_ANIMATION_SPEED = 3f;
    
    // Maximum open angle for the door
    private const float DOOR_OPEN_ANGLE = 60f;
    
    // Closed angle for the door
    private const float DOOR_CLOSED_ANGLE = 0f;
    
    // Tag to check for camera collision
    private const string CAMERA_TAG = "MainCamera";

    /// <summary>
    /// Update is called once per frame
    /// Smoothly interpolates door rotation towards target angle
    /// </summary>
    void Update() 
    {
        // Smoothly lerp current angle towards desired angle
        currentRotationAngle = Mathf.LerpAngle(
            currentRotationAngle, 
            targetRotationAngle, 
            Time.deltaTime * DOOR_ANIMATION_SPEED
        );
        
        // Apply the rotation to the door's local transform
        transform.localEulerAngles = new Vector3(currentRotationAngle, 0f, 0f);
    }

    /// <summary>
    /// Sets the target angle to open the door
    /// </summary>
    void OpenDoors() 
    {
        targetRotationAngle = DOOR_OPEN_ANGLE;
        Debug.Log("[LamboDoorBehaviour] Opening door to " + DOOR_OPEN_ANGLE + " degrees");
    }

    /// <summary>
    /// Sets the target angle to close the door
    /// </summary>
    void CloseDoors() 
    {
        targetRotationAngle = DOOR_CLOSED_ANGLE;
        Debug.Log("[LamboDoorBehaviour] Closing door to " + DOOR_CLOSED_ANGLE + " degrees");
    }

    /// <summary>
    /// Called when another collider enters the trigger zone
    /// Opens doors if the camera enters
    /// </summary>
    /// <param name="other">The collider that entered the trigger</param>
    private void OnTriggerEnter(Collider other) 
    {
        // Check if the entering collider is the main camera
        if (other.CompareTag(CAMERA_TAG)) 
        {
            OpenDoors();
        }
    }

    /// <summary>
    /// Called when another collider exits the trigger zone
    /// Closes doors if the camera exits
    /// </summary>
    /// <param name="other">The collider that exited the trigger</param>
    private void OnTriggerExit(Collider other) 
    {
        // Check if the exiting collider is the main camera
        if (other.CompareTag(CAMERA_TAG)) 
        {
            CloseDoors();
        }
    }
}
```

### Assets/Scripts/_TrackableVisibility.cs

```c#
using UnityEngine;
using Vuforia;

/// <summary>
/// Controls visibility of AR tracked objects
/// Shows/hides children based on Vuforia tracking status
/// </summary>
[RequireComponent(typeof(ObserverBehaviour))]
public class TrackableVisibility : MonoBehaviour
{
    // Reference to the Vuforia observer component
    private ObserverBehaviour observer;

    /// <summary>
    /// Initialize observer and subscribe to tracking events
    /// </summary>
    void Start()
    {
        // Get the observer component
        observer = GetComponent<ObserverBehaviour>();
        
        // Subscribe to target status changes
        if (observer != null)
        {
            observer.OnTargetStatusChanged += OnStatusChanged;
        }

        // Start hidden until target is detected
        SetChildrenActive(false);
    }

    /// <summary>
    /// Clean up event subscriptions
    /// </summary>
    void OnDestroy()
    {
        // Unsubscribe from target status changes
        if (observer != null)
        {
            observer.OnTargetStatusChanged -= OnStatusChanged;
        }
    }

    /// <summary>
    /// Handle target tracking status changes
    /// </summary>
    /// <param name="behaviour">The observer behaviour</param>
    /// <param name="status">Current tracking status</param>
    void OnStatusChanged(ObserverBehaviour behaviour, TargetStatus status)
    {
        // Determine if target is being tracked
        bool isTracked = status.Status == Status.TRACKED || status.Status == Status.EXTENDED_TRACKED;
        
        // Update children visibility based on tracking status
        SetChildrenActive(isTracked);
    }

    /// <summary>
    /// Enable or disable child renderers, colliders, and canvases
    /// Skips any children with VideoPlaneController components
    /// </summary>
    /// <param name="active">Whether to activate or deactivate children</param>
    void SetChildrenActive(bool active)
    {
        // Enable/disable child Renderers
        // Skip any child that has a VideoPlaneController (video planes are controlled explicitly)
        foreach (var r in GetComponentsInChildren<Renderer>(true))
        {
            // Check if this renderer belongs to a video plane
            if (r.GetComponentInParent<VideoPlaneController>()) 
            {
                continue;
            }
            
            r.enabled = active;
        }
        
        // Enable/disable child Colliders
        foreach (var c in GetComponentsInChildren<Collider>(true))
        {
            // Check if this collider belongs to a video plane
            if (c.GetComponentInParent<VideoPlaneController>()) 
            {
                continue;
            }
            
            c.enabled = active;
        }
        
        // Enable/disable child Canvas elements
        foreach (var cv in GetComponentsInChildren<Canvas>(true))
        {
            // Check if this canvas belongs to a video plane
            if (cv.GetComponentInParent<VideoPlaneController>()) 
            {
                continue;
            }
            
            cv.enabled = active;
        }
    }
}
```

### Assets/Scripts/_VideoPlaneController.cs

```c#
using UnityEngine;
using UnityEngine.Video;
using Vuforia;

/// <summary>
/// Controls video playback on AR video planes
/// Manages video player, renderer, and audio components
/// Automatically stops video when tracking is lost
/// </summary>
[RequireComponent(typeof(VideoPlayer))]
public class VideoPlaneController : MonoBehaviour
{
    // Core component references
    private VideoPlayer vp;
    private Renderer rend;
    private AudioSource audioSource;
    private ObserverBehaviour parentObserver;

    /// <summary>
    /// Cache component references early
    /// </summary>
    void Awake()
    {
        // Get the video player component
        vp = GetComponent<VideoPlayer>();
        
        // Get the renderer component for visibility control
        rend = GetComponent<Renderer>();
        
        // Get the audio source component if present
        audioSource = GetComponent<AudioSource>();
    }

    /// <summary>
    /// Initialize video settings and tracking events
    /// </summary>
    void Start()
    {
        // Hidden initially until explicitly played
        if (rend) 
        {
            rend.enabled = false;
        }
        
        // Don't autoplay on scene load
        if (vp) 
        {
            vp.playOnAwake = false;
        }

        // Subscribe to parent target tracking events
        parentObserver = GetComponentInParent<ObserverBehaviour>();
        if (parentObserver != null)
        {
            parentObserver.OnTargetStatusChanged += OnTargetStatusChanged;
        }
    }

    /// <summary>
    /// Clean up event subscriptions
    /// </summary>
    void OnDestroy()
    {
        // Unsubscribe from tracking events
        if (parentObserver != null)
        {
            parentObserver.OnTargetStatusChanged -= OnTargetStatusChanged;
        }
    }

    /// <summary>
    /// Handle parent target tracking status changes
    /// </summary>
    /// <param name="obs">The observer behaviour</param>
    /// <param name="status">Current tracking status</param>
    void OnTargetStatusChanged(ObserverBehaviour obs, TargetStatus status)
    {
        // Check if target is currently tracked
        bool isTracked = status.Status == Status.TRACKED || status.Status == Status.EXTENDED_TRACKED;
        
        // Stop video automatically when target is lost
        if (!isTracked)
        {
            StopAndHide();
        }
    }

    /// <summary>
    /// Start playing video and show the video plane
    /// </summary>
    public void PlayAndShow()
    {
        // Make the video plane visible
        if (rend) 
        {
            rend.enabled = true;
        }

        // Configure audio output if audio source is available
        if (audioSource != null)
        {
            vp.audioOutputMode = VideoAudioOutputMode.AudioSource;
            vp.SetTargetAudioSource(0, audioSource);
        }

        // Start video playback
        vp.Play();
    }

    /// <summary>
    /// Stop video playback and hide the video plane
    /// </summary>
    public void StopAndHide()
    {
        // Stop the video player if it's playing
        if (vp != null && vp.isPlaying) 
        {
            vp.Stop();
        }
        
        // Stop the audio source
        if (audioSource != null) 
        {
            audioSource.Stop();
        }
        
        // Hide the video plane renderer
        if (rend != null) 
        {
            rend.enabled = false;
        }
    }

    /// <summary>
    /// Check if video is currently playing
    /// </summary>
    /// <returns>True if video is playing, false otherwise</returns>
    public bool IsPlaying()
    {
        return vp != null && vp.isPlaying;
    }
}
```

### Assets/Scripts/CarFrontScript.cs

```c#
using UnityEngine;
using UnityEngine.Video;
using System.Collections;
#if VUFORIA_PRESENT
using Vuforia;
#endif

public class CarFrontScript : MonoBehaviour
{
    private Camera arCamera;

    public GameObject aboutPlane;
    public GameObject projectsPlane;

    private VideoPlayer currentVideo;

    void Start()
    {
        // ✅ Ensure correct ARCamera reference for both Vuforia and normal camera
        #if VUFORIA_PRESENT
        arCamera = VuforiaBehaviour.Instance?.transform.GetComponentInChildren<Camera>();
        #else
        arCamera = Camera.main;
        #endif

        if (arCamera == null)
        {
            Debug.LogError("[CarFrontScript] AR Camera not found!");
        }

        // ✅ Hide all video planes at start
        if (aboutPlane) aboutPlane.SetActive(false);
        if (projectsPlane) projectsPlane.SetActive(false);
    }

    void Update()
    {
        // ✅ Touch input (mobile)
        if (Input.touchCount > 0)
        {
            Touch touch = Input.GetTouch(0);
            if (touch.phase == TouchPhase.Began || touch.phase == TouchPhase.Ended)
                HandleRaycast(touch.position);
        }

        // ✅ Mouse input (Editor)
        if (Input.GetMouseButtonDown(0))
        {
            HandleRaycast(Input.mousePosition);
        }
    }

    private void HandleRaycast(Vector3 screenPosition)
    {
        if (arCamera == null) return;

        Ray ray = arCamera.ScreenPointToRay(screenPosition);
        RaycastHit hit;

        if (Physics.Raycast(ray, out hit, Mathf.Infinity, LayerMask.GetMask("Default")))
        {
            string hitName = hit.collider.gameObject.name;
            Debug.Log("[CarFrontScript] Raycast hit: " + hitName);

            switch (hitName)
            {
                case "car_email_btn":
                    Application.OpenURL("mailto:john.doe@gmail.com");
                    break;

                case "car_phone_btn":
                    Application.OpenURL("tel://5101111111");
                    break;

                case "car_map_btn":
                    Application.OpenURL("https://www.google.co.in/maps/dir//great+mall");
                    break;

                case "car_about_btn":
                    ToggleVideo(aboutPlane);
                    break;

                case "car_projects_btn":
                    ToggleVideo(projectsPlane);
                    break;

                // ✅ Allow tapping on plane itself to close
                case "aboutPlane":
                case "projectsPlane":
                    CloseCurrentVideo();
                    break;

                default:
                    Debug.Log("[CarFrontScript] Hit object not a button");
                    break;
            }
        }
    }

    private void ToggleVideo(GameObject plane)
    {
        if (plane == null) return;

        VideoPlayer vp = plane.GetComponent<VideoPlayer>();
        if (vp == null)
        {
            Debug.LogWarning("[CarFrontScript] No VideoPlayer found on " + plane.name);
            return;
        }

        // ✅ If plane already active → close it
        if (plane.activeSelf)
        {
            CloseCurrentVideo();
            return;
        }

        // ✅ Hide any currently open video plane
        if (currentVideo != null)
        {
            currentVideo.Stop();
            currentVideo.gameObject.SetActive(false);
            currentVideo = null;
        }

        // ✅ Small delay before activating to refresh colliders properly
        StartCoroutine(ActivateAndPlay(plane, vp));
    }

    private IEnumerator ActivateAndPlay(GameObject plane, VideoPlayer vp)
    {
        yield return null; // wait one frame
        plane.SetActive(true);
        vp.Play();
        currentVideo = vp;
        Debug.Log("[CarFrontScript] Playing video on " + plane.name);
    }

    private void CloseCurrentVideo()
    {
        if (currentVideo != null)
        {
            currentVideo.Stop();
            currentVideo.gameObject.SetActive(false);
            Debug.Log("[CarFrontScript] Closed video on " + currentVideo.gameObject.name);
            currentVideo = null;
        }
    }
}

```

[4 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]