Learn how to add presentation mode to your web app with this easy, step-by-step guide for a seamless user experience.

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
Why Add a Presentation Mode?
Presentation mode transforms your regular web application into a distraction-free, focused experience optimized for sharing information with an audience. It's like giving your app a "presenter persona" that knows how to put its best foot forward when all eyes are on it.
Core Benefits for Stakeholders:
1. User Interface Transformations
This layer handles how your app visually transforms when entering presentation mode.
Here's a simple CSS approach using a presentation mode class:
/* Base presentation mode styles */
.presentation-mode {
/* Increase base font size for readability */
font-size: 120%;
/* Optimize for projectors/screens with higher contrast */
--text-color: #000;
--bg-color: #fff;
color: var(--text-color);
background-color: var(--bg-color);
}
/* Hide non-essential elements */
.presentation-mode .nav-secondary,
.presentation-mode .user-controls,
.presentation-mode .notifications,
.presentation-mode .sidebar {
display: none;
}
/* Enhance primary content area */
.presentation-mode .main-content {
width: 100%;
max-width: 100%;
padding: 2rem;
}
/* Make interactive elements more prominent */
.presentation-mode button,
.presentation-mode .interactive-element {
transform: scale(1.2);
margin: 1rem;
}
2. Navigation and Control System
This layer manages how users navigate and control the presentation.
Here's a keyboard navigation implementation:
class PresentationController {
constructor() {
this.currentSlideIndex = 0;
this.slides = document.querySelectorAll('.presentation-slide');
this.isPresentationMode = false;
// Bind methods
this.handleKeyDown = this.handleKeyDown.bind(this);
this.togglePresentationMode = this.togglePresentationMode.bind(this);
}
initialize() {
// Add keyboard listener
document.addEventListener('keydown', this.handleKeyDown);
// Add presentation toggle button
const toggleButton = document.createElement('button');
toggleButton.classList.add('presentation-toggle');
toggleButton.textContent = 'Enter Presentation Mode';
toggleButton.addEventListener('click', this.togglePresentationMode);
document.body.appendChild(toggleButton);
}
handleKeyDown(event) {
if (!this.isPresentationMode) return;
switch(event.key) {
case 'ArrowRight':
case 'n':
case ' ':
this.nextSlide();
break;
case 'ArrowLeft':
case 'p':
this.previousSlide();
break;
case 'Escape':
this.togglePresentationMode();
break;
}
}
nextSlide() {
if (this.currentSlideIndex < this.slides.length - 1) {
this.goToSlide(this.currentSlideIndex + 1);
}
}
previousSlide() {
if (this.currentSlideIndex > 0) {
this.goToSlide(this.currentSlideIndex - 1);
}
}
goToSlide(index) {
// Hide current slide
this.slides[this.currentSlideIndex].classList.remove('active');
// Show new slide
this.currentSlideIndex = index;
this.slides[this.currentSlideIndex].classList.add('active');
}
togglePresentationMode() {
this.isPresentationMode = !this.isPresentationMode;
document.body.classList.toggle('presentation-mode', this.isPresentationMode);
const toggleButton = document.querySelector('.presentation-toggle');
toggleButton.textContent = this.isPresentationMode ?
'Exit Presentation Mode' : 'Enter Presentation Mode';
// When entering presentation mode, ensure first slide is active
if (this.isPresentationMode) {
this.goToSlide(0);
// Request fullscreen if supported
if (document.documentElement.requestFullscreen) {
document.documentElement.requestFullscreen();
}
} else if (document.exitFullscreen) {
document.exitFullscreen();
}
}
}
// Initialize when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
const presenter = new PresentationController();
presenter.initialize();
});
3. Content Optimization Layer
This layer adapts your content for presentation contexts.
Example content optimization implementation:
class ContentOptimizer {
constructor() {
this.presentationMode = false;
this.originalContent = new Map(); // Stores original content states
}
optimize(isPresentation) {
this.presentationMode = isPresentation;
if (isPresentation) {
this.enhanceImages();
this.optimizeCharts();
this.preloadNextSections();
this.adjustVideoPlayers();
} else {
this.restoreOriginalContent();
}
}
enhanceImages() {
const images = document.querySelectorAll('.content-image');
images.forEach(img => {
// Store original state
this.originalContent.set(img, {
src: img.src,
className: img.className,
style: img.getAttribute('style')
});
// Replace with high-res version if available
if (img.dataset.presentationSrc) {
img.src = img.dataset.presentationSrc;
}
// Enhance styling for presentation
img.classList.add('presentation-optimized');
img.style.maxWidth = '90%';
img.style.height = 'auto';
img.style.margin = '1rem auto';
img.style.display = 'block';
});
}
optimizeCharts() {
const charts = document.querySelectorAll('.chart-container');
charts.forEach(chart => {
// Store original state
this.originalContent.set(chart, {
className: chart.className,
style: chart.getAttribute('style')
});
// Apply presentation-specific styles
chart.classList.add('presentation-chart');
chart.style.height = '70vh';
chart.style.width = '90%';
// If using a chart library, you might need to redraw for the new dimensions
if (window.chartInstances && window.chartInstances[chart.id]) {
window.chartInstances[chart.id].resize();
}
});
}
preloadNextSections() {
// Find elements that would be shown in the next few "slides"
const upcomingContent = document.querySelectorAll('.presentation-slide:not(.active) img, .presentation-slide:not(.active) video');
// Preload images and videos
upcomingContent.forEach(element => {
if (element.tagName === 'IMG' && !element.complete) {
// Preload image
const preloader = new Image();
preloader.src = element.src;
} else if (element.tagName === 'VIDEO' && element.networkState === 0) {
// Preload video
element.load();
}
});
}
adjustVideoPlayers() {
const videos = document.querySelectorAll('video');
videos.forEach(video => {
// Store original state
this.originalContent.set(video, {
controls: video.controls,
autoplay: video.autoplay,
className: video.className,
style: video.getAttribute('style')
});
// Optimize for presentation
video.controls = true;
video.autoplay = false; // Let presenter control when video plays
video.classList.add('presentation-video');
video.style.maxWidth = '90%';
video.style.height = 'auto';
video.style.margin = '0 auto';
video.style.display = 'block';
});
}
restoreOriginalContent() {
// Restore all elements to their original state
this.originalContent.forEach((originalState, element) => {
if (originalState.src) element.src = originalState.src;
element.className = originalState.className;
if (originalState.style) {
element.setAttribute('style', originalState.style);
} else {
element.removeAttribute('style');
}
if ('controls' in originalState) element.controls = originalState.controls;
if ('autoplay' in originalState) element.autoplay = originalState.autoplay;
});
// Clear the stored original states
this.originalContent.clear();
}
}
// Usage
const contentOptimizer = new ContentOptimizer();
document.addEventListener('presentation-mode-change', (e) => {
contentOptimizer.optimize(e.detail.isPresentation);
});
4. Speaker Tools Layer
These features help the presenter deliver effectively.
Implementation of presenter notes and tools:
class PresenterTools {
constructor() {
this.notesVisible = false;
this.dualScreenMode = false;
this.pointerActive = false;
this.startTime = null;
// Create UI elements
this.createToolbar();
this.createNotesPanel();
this.createTimer();
this.createPointer();
// Bind event handlers
this.toggleNotes = this.toggleNotes.bind(this);
this.resetTimer = this.resetTimer.bind(this);
this.handlePointer = this.handlePointer.bind(this);
this.updatePointerPosition = this.updatePointerPosition.bind(this);
}
createToolbar() {
const toolbar = document.createElement('div');
toolbar.classList.add('presenter-toolbar');
toolbar.innerHTML = `
<button class="notes-toggle">Notes</button>
<button class="timer-toggle">Timer</button>
<button class="pointer-toggle">Pointer</button>
<div class="elapsed-time">00:00</div>
`;
// Only visible in presentation mode
toolbar.style.display = 'none';
document.body.appendChild(toolbar);
// Set up event listeners
toolbar.querySelector('.notes-toggle').addEventListener('click', this.toggleNotes);
toolbar.querySelector('.timer-toggle').addEventListener('click', this.resetTimer);
toolbar.querySelector('.pointer-toggle').addEventListener('click', this.handlePointer);
}
createNotesPanel() {
const notesPanel = document.createElement('div');
notesPanel.classList.add('presenter-notes');
notesPanel.style.display = 'none';
document.body.appendChild(notesPanel);
// Load notes from data attributes
this.updateNotesForSlide(0);
}
updateNotesForSlide(slideIndex) {
const slide = document.querySelectorAll('.presentation-slide')[slideIndex];
const notesPanel = document.querySelector('.presenter-notes');
if (slide && slide.dataset.presenterNotes) {
notesPanel.innerHTML = `<h3>Slide ${slideIndex + 1} Notes</h3><div>${slide.dataset.presenterNotes}</div>`;
} else {
notesPanel.innerHTML = `<h3>Slide ${slideIndex + 1} Notes</h3><div>No notes for this slide.</div>`;
}
}
toggleNotes() {
this.notesVisible = !this.notesVisible;
const notesPanel = document.querySelector('.presenter-notes');
notesPanel.style.display = this.notesVisible ? 'block' : 'none';
// Highlight active button
document.querySelector('.notes-toggle').classList.toggle('active', this.notesVisible);
}
createTimer() {
this.timerInterval = null;
this.startTime = null;
// Timer already created in toolbar
}
resetTimer() {
// Clear existing interval
if (this.timerInterval) {
clearInterval(this.timerInterval);
}
// Reset or start timer
if (this.startTime === null) {
// Start timer
this.startTime = Date.now();
document.querySelector('.timer-toggle').textContent = 'Reset Timer';
document.querySelector('.timer-toggle').classList.add('active');
this.timerInterval = setInterval(() => {
const elapsed = Math.floor((Date.now() - this.startTime) / 1000);
const minutes = Math.floor(elapsed / 60).toString().padStart(2, '0');
const seconds = (elapsed % 60).toString().padStart(2, '0');
document.querySelector('.elapsed-time').textContent = `${minutes}:${seconds}`;
}, 1000);
} else {
// Reset timer
this.startTime = null;
document.querySelector('.elapsed-time').textContent = '00:00';
document.querySelector('.timer-toggle').textContent = 'Start Timer';
document.querySelector('.timer-toggle').classList.remove('active');
}
}
createPointer() {
const pointer = document.createElement('div');
pointer.classList.add('presentation-pointer');
pointer.style.position = 'absolute';
pointer.style.width = '20px';
pointer.style.height = '20px';
pointer.style.borderRadius = '50%';
pointer.style.backgroundColor = 'red';
pointer.style.opacity = '0.7';
pointer.style.transform = 'translate(-50%, -50%)';
pointer.style.pointerEvents = 'none'; // Don't interfere with clicks
pointer.style.zIndex = '9999';
pointer.style.display = 'none';
document.body.appendChild(pointer);
}
handlePointer() {
this.pointerActive = !this.pointerActive;
const pointer = document.querySelector('.presentation-pointer');
pointer.style.display = this.pointerActive ? 'block' : 'none';
// Highlight active button
document.querySelector('.pointer-toggle').classList.toggle('active', this.pointerActive);
if (this.pointerActive) {
document.addEventListener('mousemove', this.updatePointerPosition);
} else {
document.removeEventListener('mousemove', this.updatePointerPosition);
}
}
updatePointerPosition(event) {
const pointer = document.querySelector('.presentation-pointer');
pointer.style.left = `${event.clientX}px`;
pointer.style.top = `${event.clientY}px`;
}
show(isVisible) {
const toolbar = document.querySelector('.presenter-toolbar');
toolbar.style.display = isVisible ? 'flex' : 'none';
// Hide notes and pointer when exiting presentation mode
if (!isVisible) {
document.querySelector('.presenter-notes').style.display = 'none';
document.querySelector('.presentation-pointer').style.display = 'none';
this.notesVisible = false;
this.pointerActive = false;
// Clear timer
if (this.timerInterval) {
clearInterval(this.timerInterval);
this.startTime = null;
document.querySelector('.elapsed-time').textContent = '00:00';
document.querySelector('.timer-toggle').textContent = 'Start Timer';
}
// Remove event listener
document.removeEventListener('mousemove', this.updatePointerPosition);
}
}
notifySlideChange(slideIndex) {
this.updateNotesForSlide(slideIndex);
}
}
// Usage
const presenterTools = new PresenterTools();
// Connect to presentation controller
document.addEventListener('presentation-mode-change', (e) => {
presenterTools.show(e.detail.isPresentation);
});
document.addEventListener('slide-change', (e) => {
presenterTools.notifySlideChange(e.detail.slideIndex);
});
5. Display Configuration Layer
This layer handles technical aspects of presenting across different display setups.
Implementation of display configuration:
class DisplayManager {
constructor() {
this.inPresentationMode = false;
this.primaryDisplay = null;
this.secondaryDisplay = null;
this.presenterWindow = null;
// Bind methods
this.toggleFullscreen = this.toggleFullscreen.bind(this);
this.handleDisplayChange = this.handleDisplayChange.bind(this);
this.setupDualDisplay = this.setupDualDisplay.bind(this);
this.handleWindowMessage = this.handleWindowMessage.bind(this);
// Set up event listeners
window.addEventListener('resize', this.handleDisplayChange);
document.addEventListener('fullscreenchange', this.handleDisplayChange);
}
initialize() {
// Create display control UI
const displayControls = document.createElement('div');
displayControls.classList.add('display-controls');
displayControls.innerHTML = `
<button class="fullscreen-toggle">Fullscreen</button>
<button class="dual-display-toggle">Presenter View</button>
`;
document.body.appendChild(displayControls);
// Set up event listeners
displayControls.querySelector('.fullscreen-toggle').addEventListener('click', this.toggleFullscreen);
displayControls.querySelector('.dual-display-toggle').addEventListener('click', this.setupDualDisplay);
// Listen for messages from presenter window
window.addEventListener('message', this.handleWindowMessage);
}
toggleFullscreen() {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen().catch(err => {
console.error(`Error attempting to enable fullscreen: ${err.message}`);
});
} else {
if (document.exitFullscreen) {
document.exitFullscreen();
}
}
}
handleDisplayChange() {
// Update UI based on current state
const isFullscreen = !!document.fullscreenElement;
document.querySelector('.fullscreen-toggle').textContent = isFullscreen ? 'Exit Fullscreen' : 'Fullscreen';
// Dispatch event for other components
const event = new CustomEvent('display-configuration-change', {
detail: {
isFullscreen,
width: window.innerWidth,
height: window.innerHeight,
aspectRatio: window.innerWidth / window.innerHeight
}
});
document.dispatchEvent(event);
// Apply responsive adjustments
this.applyResponsiveAdjustments();
}
applyResponsiveAdjustments() {
const aspectRatio = window.innerWidth / window.innerHeight;
// Apply different styles based on aspect ratio
if (aspectRatio > 1.8) { // Very wide display
document.body.classList.add('wide-display');
document.body.classList.remove('standard-display', 'narrow-display');
} else if (aspectRatio < 1.3) { // Narrow display
document.body.classList.add('narrow-display');
document.body.classList.remove('standard-display', 'wide-display');
} else { // Standard display
document.body.classList.add('standard-display');
document.body.classList.remove('wide-display', 'narrow-display');
}
// Adjust font sizing
const baseFontSize = Math.min(window.innerWidth / 50, window.innerHeight / 30);
document.documentElement.style.fontSize = `${baseFontSize}px`;
}
setupDualDisplay() {
if (this.presenterWindow && !this.presenterWindow.closed) {
this.presenterWindow.focus();
return;
}
// Configuration for presenter window
const config = {
slideIndex: currentSlideIndex, // Assuming this variable exists in global scope
notes: this.collectPresenterNotes(),
totalSlides: document.querySelectorAll('.presentation-slide').length
};
// Open presenter window
const presenterUrl = new URL('presenter.html', window.location.href);
this.presenterWindow = window.open(
presenterUrl.href,
'presenter_view',
'width=800,height=600,menubar=no,toolbar=no,location=no'
);
// Wait for window to load then send configuration
this.presenterWindow.addEventListener('load', () => {
this.presenterWindow.postMessage({
type: 'presenter-config',
config
}, '*');
});
// Update UI
document.querySelector('.dual-display-toggle').textContent = 'Presenter View Open';
}
collectPresenterNotes() {
const slides = document.querySelectorAll('.presentation-slide');
const notes = [];
slides.forEach(slide => {
notes.push(slide.dataset.presenterNotes || '');
});
return notes;
}
handleWindowMessage(event) {
// Handle messages from presenter window
if (!event.data || !event.data.type) return;
switch(event.data.type) {
case 'slide-change':
// Change slide in main window based on presenter window action
const slideEvent = new CustomEvent('go-to-slide', {
detail: { slideIndex: event.data.slideIndex }
});
document.dispatchEvent(slideEvent);
break;
case 'presenter-window-closed':
// Update UI when presenter window closes
document.querySelector('.dual-display-toggle').textContent = 'Presenter View';
this.presenterWindow = null;
break;
}
}
notifySlideChange(slideIndex) {
// Notify presenter window about slide change in main window
if (this.presenterWindow && !this.presenterWindow.closed) {
this.presenterWindow.postMessage({
type: 'main-slide-change',
slideIndex
}, '*');
}
}
}
// Usage
const displayManager = new DisplayManager();
document.addEventListener('DOMContentLoaded', () => {
displayManager.initialize();
});
// Connect to presentation controller
document.addEventListener('slide-change', (e) => {
displayManager.notifySlideChange(e.detail.slideIndex);
});
1. The Main Controller
class PresentationModeController {
constructor() {
// Initialize all components
this.presentationController = new PresentationController();
this.contentOptimizer = new ContentOptimizer();
this.presenterTools = new PresenterTools();
this.displayManager = new DisplayManager();
// Set up event handling
this.setupEventHandlers();
}
setupEventHandlers() {
// Listen for presentation mode toggle
document.addEventListener('presentation-mode-change', (e) => {
const isPresentation = e.detail.isPresentation;
// Update document class for CSS hooks
document.body.classList.toggle('presentation-mode', isPresentation);
// Notify components
this.contentOptimizer.optimize(isPresentation);
this.presenterTools.show(isPresentation);
// Trigger analytics
this.logPresentationEvent(isPresentation ? 'start' : 'end');
});
// Listen for slide changes
document.addEventListener('slide-change', (e) => {
const slideIndex = e.detail.slideIndex;
// Update components
this.presenterTools.notifySlideChange(slideIndex);
this.displayManager.notifySlideChange(slideIndex);
// Trigger slide change analytics
this.logSlideView(slideIndex);
});
// Listen for display changes
document.addEventListener('display-configuration-change', (e) => {
// Any specific handling for display changes
});
}
logPresentationEvent(action) {
// Analytics logging
if (typeof analytics !== 'undefined') {
analytics.track('Presentation Mode', {
action,
timestamp: new Date().toISOString(),
url: window.location.href
});
}
}
logSlideView(slideIndex) {
// Analytics logging
if (typeof analytics !== 'undefined') {
analytics.track('Presentation Slide View', {
slideIndex,
slideName: this.getSlideTitle(slideIndex),
timestamp: new Date().toISOString()
});
}
}
getSlideTitle(slideIndex) {
const slide = document.querySelectorAll('.presentation-slide')[slideIndex];
const titleElement = slide.querySelector('h1, h2, h3');
return titleElement ? titleElement.textContent : `Slide ${slideIndex + 1}`;
}
initialize() {
// Initialize all components
this.presentationController.initialize();
this.displayManager.initialize();
// Add presentation mode toggle to main UI
const mainToggle = document.createElement('button');
mainToggle.classList.add('main-presentation-toggle');
mainToggle.textContent = 'Presentation Mode';
mainToggle.addEventListener('click', () => {
const event = new CustomEvent('presentation-mode-change', {
detail: { isPresentation: !document.body.classList.contains('presentation-mode') }
});
document.dispatchEvent(event);
});
// Add to app's main navigation or appropriate location
const targetContainer = document.querySelector('.app-navigation') || document.body;
targetContainer.appendChild(mainToggle);
}
}
// Initialize when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
const presentationMode = new PresentationModeController();
presentationMode.initialize();
});
2. HTML Structure for Presentation-Ready Content
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Your Application - Presentation Ready</title>
<link rel="stylesheet" href="main.css">
<link rel="stylesheet" href="presentation.css">
</head>
<body>
<header class="app-header">
<div class="logo">Your App</div>
<nav class="app-navigation">
<!-- Navigation items -->
<button class="main-presentation-toggle">Presentation Mode</button>
</nav>
<div class="user-controls">
<!-- User controls that will be hidden in presentation mode -->
</div>
</header>
<div class="app-container">
<aside class="sidebar">
<!-- Sidebar content that will be hidden in presentation mode -->
</aside>
<main class="main-content">
<!-- This becomes the presentation container -->
<div class="presentation-slide" data-presenter-notes="Introduce the key metrics for Q2 performance">
<h2>Q2 Performance Overview</h2>
<div class="chart-container" id="revenue-chart">
<!-- Chart content -->
</div>
<p class="highlight">Revenue increased by 24% compared to Q1</p>
</div>
<div class="presentation-slide" data-presenter-notes="Emphasize the mobile growth numbers, they're impressive">
<h2>Mobile User Growth</h2>
<img src="user-growth.png" data-presentation-src="user-growth-hires.png" alt="User growth chart" class="content-image">
<ul class="key-points">
<li>iOS users: +15%</li>
<li>Android users: +22%</li>
<li>Mobile web: +8%</li>
</ul>
</div>
<div class="presentation-slide" data-presenter-notes="Demo the new checkout flow live">
<h2>New Checkout Experience</h2>
<video controls src="checkout-demo.mp4" class="feature-demo"></video>
<div class="demo-controls">
<!-- Interactive elements for the demo -->
</div>
</div>
</main>
</div>
<footer class="app-footer">
<!-- Footer that will be hidden in presentation mode -->
</footer>
<!-- Scripts -->
<script src="presentation-controller.js"></script>
<script src="content-optimizer.js"></script>
<script src="presenter-tools.js"></script>
<script src="display-manager.js"></script>
<script src="presentation-mode-controller.js"></script>
</body>
</html>
Start Small, Then Expand
Performance Considerations
Testing Across Environments
Tracking Presentation Mode Usage
// Add to your PresentationModeController class
trackUsage() {
// Track entrance to presentation mode
document.addEventListener('presentation-mode-change', (e) => {
if (e.detail.isPresentation) {
analytics.track('Presentation Mode Started', {
timestamp: new Date().toISOString(),
url: window.location.href,
userRole: currentUser.role, // Assuming you have this info
screenSize: `${window.innerWidth}x${window.innerHeight}`
});
// Start timing the presentation
this.presentationStartTime = Date.now();
} else if (this.presentationStartTime) {
// Track exit from presentation mode with duration
const durationSeconds = Math.round((Date.now() - this.presentationStartTime) / 1000);
analytics.track('Presentation Mode Ended', {
timestamp: new Date().toISOString(),
durationSeconds,
slidesViewed: this.slidesViewed.length,
url: window.location.href
});
this.presentationStartTime = null;
this.slidesViewed = [];
}
});
// Track individual slide views
document.addEventListener('slide-change', (e) => {
if (document.body.classList.contains('presentation-mode')) {
const slideIndex = e.detail.slideIndex;
// Record unique slides viewed
if (!this.slidesViewed.includes(slideIndex)) {
this.slidesViewed.push(slideIndex);
}
analytics.track('Presentation Slide View', {
slideIndex,
slideTitle: this.getSlideTitle(slideIndex),
timestamp: new Date().toISOString()
});
}
});
}
Key Metrics to Monitor
Adding presentation mode to your web app isn't just about mimicking PowerPoint. It's about recognizing that your application is increasingly the focal point of important business conversations.
When implemented well, presentation mode transforms your product from "a tool we need to export data from before presenting" to "our go-to platform for sharing insights." It keeps your application at the center of decision-making processes and elevates the perceived value of your product.
The implementation approach outlined here balances technical feasibility with user experience concerns, allowing you to start simple and expand as the feature proves valuable.
Remember: In the age of remote work and digital-first communication, every application that can elegantly transition into a presentation tool has a distinct advantage over those that can't.
Explore the top 3 ways presentation mode enhances user experience in your web app.
From startups to enterprises and everything in between, see for yourself our incredible impact.
Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We’ll discuss your project and provide a custom quote at no cost.Â