1. Project Overview & Features
The 3D Radial Share Arc Button Animation is an essential frontend project designed to help you bridge the gap between learning JavaScript syntax and building interactive, responsive web applications.
Key Features Implemented:
- Sleek white 'SHARE' capsule pill button with soft diffuse drop shadows and uppercase tracking
- Smooth spring-damping morph from capsule into central 'β' close button and 'Link copied' hub
- 5 Floating circular social channel discs with crystal-clear vector brand icons (π, LinkedIn, WhatsApp, Email, Copy Link)
- Exact CSS trigonometric arc placement math with live pitch angle (25Β°-65Β°) and radius sliders
- 3D card perspective tilt & spatial elevation on mouse move and touch drag
- Pentatonic chime audio synthesizer with distinct musical frequencies per channel on hover
- One-click native clipboard link copying with celebration confetti burst
- Interactive code inspector dock with tabs for index.html, app.css, and app.js
2. Step-by-Step Code Walkthrough
A Step 1: Semantic HTML Structure
We structure the interface using clean, semantic HTML elements. This ensures maximum accessibility and provides intuitive hooks for CSS classes and JavaScript query selectors.
<div class="radial-share-app" id="radialShareApp">
<!-- Confetti Canvas for Celebration -->
<canvas id="confettiCanvas" class="confetti-canvas"></canvas>
<!-- Top Studio Header -->
<header class="app-header">
<div class="header-left">
<div class="badge-pill"><i class="fas fa-share-nodes text-indigo"></i> 3D UI Motion Design</div>
<h1 class="header-title">Share Button Animation</h1>
</div>
<div class="header-right">
<button type="button" class="dock-btn" id="btnAudioToggle" title="Toggle Sound FX">
<i class="fas fa-volume-up"></i>
</button>
<button type="button" class="dock-btn" id="btnThemeToggle" title="Switch Theme">
<i class="fas fa-palette"></i>
</button>
<button type="button" class="dock-btn" id="btn3DTiltToggle" title="Toggle 3D Gyro/Tilt Effect">
<i class="fas fa-cube"></i>
</button>
<button type="button" class="dock-btn" id="btnOpenConfig" title="Share Settings">
<i class="fas fa-sliders-h"></i>
</button>
</div>
</header>
<!-- Main Showcase Canvas Card (Matching @_code_and_chill_ Viral Design) -->
<main class="showcase-viewport">
<div class="share-card-container" id="shareCard">
<div class="share-card-stage" id="shareStage">
<!-- Center Trigger & Splay Hub -->
<div class="share-hub" id="shareHub">
<!-- 5 Radial Social Channel Discs -->
<nav class="splay-arc" id="splayArc">
<!-- 0: X (Twitter) -->
<button type="button" class="ch" data-ch="x" data-index="0" style="--i: 0;" title="Share to X">
<span class="ch-body">
<svg viewBox="0 0 24 24" class="ch-icon">
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>
</svg>
</span>
</button>
<!-- 1: LinkedIn -->
<button type="button" class="ch" data-ch="linkedin" data-index="1" style="--i: 1;" title="Share to LinkedIn">
<span class="ch-body">
<svg viewBox="0 0 24 24" class="ch-icon text-linkedin">
<path d="M19 3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14m-.5 15.5v-5.3a3.26 3.26 0 0 0-3.26-3.26c-.85 0-1.84.52-2.28 1.3v-1.11h-2.79v8.37h2.79v-4.93c0-.77.62-1.4 1.39-1.4a1.4 1.4 0 0 1 1.4 1.4v4.93h2.75M6.88 8.56a1.68 1.68 0 0 0 1.68-1.68c0-.93-.75-1.69-1.68-1.69a1.69 1.69 0 0 0-1.69 1.69c0 .93.76 1.68 1.69 1.68m1.39 9.94v-8.37H5.5v8.37h2.77z"/>
</svg>
</span>
</button>
<!-- 2: WhatsApp (Top Center) -->
<button type="button" class="ch" data-ch="whatsapp" data-index="2" style="--i: 2;" title="Share to WhatsApp">
<span class="ch-body">
<svg viewBox="0 0 24 24" class="ch-icon text-whatsapp">
<path d="M12.04 2c-5.46 0-9.91 4.45-9.91 9.91 0 1.75.46 3.45 1.32 4.95L2.05 22l5.25-1.38c1.45.79 3.08 1.21 4.74 1.21 5.46 0 9.91-4.45 9.91-9.91 0-2.65-1.03-5.14-2.9-7.01A9.816 9.816 0 0 0 12.04 2m.01 1.67c4.54 0 8.24 3.7 8.24 8.24 0 2.2-.86 4.27-2.42 5.82a8.188 8.188 0 0 1-5.82 2.42c-1.47 0-2.91-.39-4.18-1.14l-.3-.18-3.11.82.83-3.03-.2-.31a8.196 8.196 0 0 1-1.26-4.38c0-4.54 3.7-8.24 8.24-8.24m4.54 11.66c-.25-.13-1.47-.72-1.7-.81-.23-.08-.39-.13-.56.13-.17.25-.64.81-.79.97-.14.17-.29.19-.54.06-.25-.13-1.06-.39-2.01-1.24-.74-.66-1.24-1.48-1.39-1.73-.14-.25-.02-.39.11-.51.11-.11.25-.29.37-.43s.17-.25.25-.42c.08-.17.04-.31-.02-.44s-.56-1.35-.77-1.85c-.2-.48-.41-.42-.56-.43h-.48c-.17 0-.44.06-.67.31-.23.25-.87.85-.87 2.07s.89 2.4 1.01 2.57c.13.17 1.75 2.67 4.24 3.74.59.26 1.05.41 1.41.53.59.19 1.13.16 1.56.1.48-.07 1.47-.6 1.68-1.18.21-.58.21-1.07.15-1.18-.07-.12-.23-.19-.48-.31z"/>
</svg>
</span>
</button>
<!-- 3: Email -->
<button type="button" class="ch" data-ch="email" data-index="3" style="--i: 3;" title="Share via Email">
<span class="ch-body">
<svg viewBox="0 0 24 24" class="ch-icon text-email">
<path d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z"/>
</svg>
</span>
</button>
<!-- 4: Copy Link -->
<button type="button" class="ch" data-ch="link" data-index="4" style="--i: 4;" title="Copy Page Link">
<span class="ch-body">
<svg viewBox="0 0 24 24" class="ch-icon text-link" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path>
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
</span>
</button>
</nav>
<!-- Central Interactive Trigger Button (SHARE Pill <-> Close Circle <-> Copied State) -->
<button type="button" class="hub-btn" id="hubBtn" aria-label="Toggle Share Menu">
<!-- Mode 1: SHARE Pill Label -->
<span class="hub-label" id="hubLabel">SHARE</span>
<!-- Mode 2: Close Icon (β) -->
<span class="hub-close" id="hubClose">
<svg viewBox="0 0 24 24" width="22" height="22" stroke="currentColor" stroke-width="2.6" stroke-linecap="round">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</span>
<!-- Mode 3: Link Copied Icon (π) -->
<span class="hub-success" id="hubSuccess">
<svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path>
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
</span>
</button>
<!-- Subtitle Status Text Below Hub -->
<div class="hub-subtitle-wrap">
<span class="hub-subtitle" id="hubSubtitle">Pick a channel</span>
</div>
</div>
</div>
</div>
</main>
<!-- Bottom Interactive Code & Trigonometry Inspector Dock -->
<div class="bottom-inspector-dock" id="bottomDock">
<div class="dock-header">
<div class="dock-tabs">
<button type="button" class="dock-tab active" data-tab="trig"><i class="fas fa-compass-drafting me-1"></i> Arc Math</button>
<button type="button" class="dock-tab" data-tab="html"><i class="fab fa-html5 me-1 text-orange"></i> index.html</button>
<button type="button" class="dock-tab" data-tab="css"><i class="fab fa-css3-alt me-1 text-blue"></i> app.css</button>
<button type="button" class="dock-tab" data-tab="js"><i class="fab fa-js me-1 text-yellow"></i> app.js</button>
</div>
<div class="dock-actions">
<button type="button" class="btn-toggle-dock" id="btnToggleDock" title="Collapse / Expand Code Dock">
<i class="fas fa-chevron-down" id="dockChevron"></i>
</button>
</div>
</div>
<!-- Tab 1: Trigonometric Splay Controls -->
<div class="tab-panel active" id="panelTrig">
<div class="trig-controls-grid">
<div class="trig-control">
<div class="control-header">
<span class="ctrl-label">Arc Pitch Angle:</span>
<strong class="ctrl-val" id="valPitch">46°</strong>
</div>
<input type="range" class="range-slider" id="sliderPitch" min="25" max="65" value="46" step="1">
</div>
<div class="trig-control">
<div class="control-header">
<span class="ctrl-label">Arc Radius:</span>
<strong class="ctrl-val" id="valRadius">155px</strong>
</div>
<input type="range" class="range-slider" id="sliderRadius" min="110" max="210" value="155" step="1">
</div>
<div class="trig-control">
<div class="control-header">
<span class="ctrl-label">Spring Tension:</span>
<strong class="ctrl-val" id="valBounciness">Bouncy</strong>
</div>
<div class="preset-btn-group">
<button type="button" class="btn-preset active" data-preset="bouncy">Bouncy</button>
<button type="button" class="btn-preset" data-preset="smooth">Smooth</button>
<button type="button" class="btn-preset" data-preset="snappy">Snappy</button>
</div>
</div>
</div>
</div>
<!-- Tab 2: HTML Snippet -->
<div class="tab-panel" id="panelHtml">
<pre class="code-snippet"><code class="language-html"><!-- Radial Share Arc Structure -->
<div class="share-hub">
<nav class="splay-arc">
<a class="ch" data-ch="x" style="--i: 0;"><svg class="ch-icon">...</svg></a>
<a class="ch" data-ch="linkedin" style="--i: 1;"><svg class="ch-icon">...</svg></a>
<a class="ch" data-ch="whatsapp" style="--i: 2;"><svg class="ch-icon">...</svg></a>
<a class="ch" data-ch="email" style="--i: 3;"><svg class="ch-icon">...</svg></a>
<a class="ch" data-ch="link" style="--i: 4;"><svg class="ch-icon">...</svg></a>
</nav>
<button class="hub-btn">SHARE</button>
</div></code></pre>
</div>
<!-- Tab 3: CSS Trigonometry Snippet -->
<div class="tab-panel" id="panelCss">
<pre class="code-snippet"><code class="language-css">/* THE ARC IS SOLVED FOR, NOT PLACED */
.splay-arc {
--pitch: 46deg;
--gap: 1.5;
--r: calc((var(--d) * var(--gap)) / (2 * sin(var(--pitch) / 2)));
}
.ch {
--a: calc((var(--i) - (var(--n) - 1) / 2) * var(--pitch));
transform: translate(
calc(sin(var(--a)) * var(--r)),
calc(cos(var(--a)) * var(--r) * -1)
) scale(var(--s));
transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
}</code></pre>
</div>
<!-- Tab 4: JS Snippet -->
<div class="tab-panel" id="panelJs">
<pre class="code-snippet"><code class="language-javascript">// Native Clipboard Write & Action Trigger
async function copyLink(url) {
await navigator.clipboard.writeText(url);
setHubState('copied', 'Link copied');
fireConfetti();
}</code></pre>
</div>
</div>
<!-- Share URL & Message Config Modal -->
<div class="custom-modal-overlay hidden" id="configModal">
<div class="custom-modal-dialog">
<div class="modal-head">
<h4><i class="fas fa-sliders-h text-indigo me-2"></i> Share Settings & Content</h4>
<button type="button" class="btn-close-modal" id="btnCloseConfig">×</button>
</div>
<div class="modal-body">
<div class="form-group mb-3">
<label class="form-label font-semibold">Share URL / Link</label>
<input type="text" class="form-control" id="inputShareUrl" value="https://example.com/share-magic">
</div>
<div class="form-group mb-3">
<label class="form-label font-semibold">Share Title / Message</label>
<textarea class="form-control" id="inputShareTitle" rows="2">Check out this awesome 3D Radial Share Button interaction! β¨</textarea>
</div>
</div>
<div class="modal-foot">
<button type="button" class="btn-modal-primary" id="btnSaveConfig">Save Settings</button>
</div>
</div>
</div>
</div>B Step 2: Styling & Responsive CSS
Modern CSS flexbox and grid layouts are used to make the UI look polished, centered, and responsive across desktop, tablet, and mobile screens.
/* 3D Radial Share Arc Button Styling */
:root {
--bg-app: #0d0f14;
--bg-stage: #ded8cb; /* Warm sand/taupe matching the viral reel */
--bg-stage-border: #cdc6b7;
--text-main: #0f172a;
--text-sub: #5c5850;
--indigo-primary: #6366f1;
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
/* Radial Splay Trigonometric Math Variables */
--n: 5; /* total items */
--pitch: 46deg; /* angle between channels */
--r: 155px; /* radius of splay arc */
--s: 0; /* scale of channels (0 when closed, 1 when open) */
--d: 58px; /* channel disc size */
}
.theme-dark {
--bg-app: #090a0f;
--bg-stage: #1e2430;
--bg-stage-border: #333d4e;
--text-main: #ffffff;
--text-sub: #94a3b8;
}
.theme-mint {
--bg-app: #0f172a;
--bg-stage: #bdd6bd;
--bg-stage-border: #a8c5a8;
--text-main: #0f172a;
--text-sub: #334155;
}
.theme-sunset {
--bg-app: #18181b;
--bg-stage: #f5ebe0;
--bg-stage-border: #e6dacb;
--text-main: #18181b;
--text-sub: #71717a;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.radial-share-app {
position: relative;
width: 100%;
height: 100vh;
min-height: 640px;
background-color: var(--bg-app);
font-family: var(--font-sans);
color: #ffffff;
overflow: hidden;
user-select: none;
display: flex;
flex-direction: column;
}
/* Confetti Canvas */
.confetti-canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 50;
pointer-events: none;
}
/* Top App Header */
.app-header {
position: absolute;
top: 18px;
left: 24px;
right: 24px;
display: flex;
justify-content: space-between;
align-items: center;
z-index: 25;
}
.header-left, .header-right {
display: flex;
align-items: center;
gap: 12px;
}
.badge-pill {
display: inline-flex;
align-items: center;
gap: 6px;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
padding: 5px 12px;
border-radius: 9999px;
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
color: #e2e8f0;
border: 1px solid rgba(255, 255, 255, 0.15);
}
.badge-pill .text-indigo {
color: var(--indigo-primary);
}
.header-title {
font-size: 1.15rem;
font-weight: 800;
color: #ffffff;
letter-spacing: -0.02em;
}
/* Dock Utility Buttons */
.dock-btn {
width: 38px;
height: 38px;
border-radius: 11px;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.15);
color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.92rem;
cursor: pointer;
transition: all 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.dock-btn:hover {
transform: translateY(-2px);
background: rgba(255, 255, 255, 0.2);
color: var(--indigo-primary);
}
.dock-btn:active {
transform: scale(0.94);
}
/* Main Showcase Viewport */
.showcase-viewport {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
perspective: 1200px;
padding: 80px 20px 140px 20px;
}
/* The Beige/Sand Stage Card Container */
.share-card-container {
position: relative;
width: 100%;
max-width: 440px;
height: 380px;
border-radius: 28px;
background-color: var(--bg-stage);
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5), 0 0 0 1px var(--bg-stage-border);
display: flex;
align-items: center;
justify-content: center;
transform-style: preserve-3d;
transition: transform 0.2s ease-out, background-color 0.4s ease;
}
.share-card-stage {
position: relative;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
transform-style: preserve-3d;
}
/* The Share Hub Central Anchor */
.share-hub {
position: relative;
width: 62px;
height: 62px;
display: flex;
align-items: center;
justify-content: center;
transform-style: preserve-3d;
}
/* 5 Radial Social Channel Discs */
.splay-arc {
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
transform-style: preserve-3d;
pointer-events: none;
}
.share-hub.is-open .splay-arc {
pointer-events: auto;
}
/* Individual Channel Button */
.ch {
position: absolute;
top: calc(var(--d) * -0.5);
left: calc(var(--d) * -0.5);
width: var(--d);
height: var(--d);
border-radius: 50%;
background: transparent;
border: none;
outline: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
/* Trigonometric Arc Placement Formula */
--a: calc((var(--i) - (var(--n) - 1) / 2) * var(--pitch));
transform: translate(
calc(sin(var(--a)) * var(--r)),
calc(cos(var(--a)) * var(--r) * -1)
) scale(var(--s)) translateZ(25px);
transition: transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
transform-style: preserve-3d;
}
.ch-body {
width: 100%;
height: 100%;
border-radius: 50%;
background: #ffffff;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 12px 26px rgba(0, 0, 0, 0.13), 0 3px 8px rgba(0, 0, 0, 0.06);
transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.25s ease;
}
.ch:hover .ch-body {
transform: scale(1.18) translateZ(15px);
box-shadow: 0 18px 36px rgba(0, 0, 0, 0.2), 0 6px 14px rgba(0, 0, 0, 0.08);
}
.ch:active .ch-body {
transform: scale(0.92);
}
.ch-icon {
width: 24px;
height: 24px;
display: block;
}
/* Specific Channel Brand Colors */
.text-linkedin { color: #0077b5; fill: #0077b5; }
.text-whatsapp { color: #25d366; fill: #25d366; width: 28px; height: 28px; }
.text-email { color: #ea4335; fill: #ea4335; }
.text-link { color: #334155; stroke: #334155; }
/* The Central Hub Button */
.hub-btn {
position: absolute;
width: 200px;
height: 62px;
border-radius: 9999px;
background: #ffffff;
border: none;
outline: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 16px 32px rgba(0, 0, 0, 0.12), 0 4px 10px rgba(0, 0, 0, 0.05);
transition: all 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
transform-style: preserve-3d;
z-index: 10;
}
.hub-btn:hover {
transform: translateY(-2px) scale(1.02);
box-shadow: 0 20px 38px rgba(0, 0, 0, 0.16), 0 6px 14px rgba(0, 0, 0, 0.08);
}
.hub-btn:active {
transform: translateY(1px) scale(0.98);
}
/* "SHARE" Pill Typography */
.hub-label {
font-size: 15px;
font-weight: 800;
letter-spacing: 0.15em;
color: #111827;
transition: opacity 0.25s ease, transform 0.25s ease;
display: block;
}
.hub-close, .hub-success {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transform: scale(0.5) rotate(-90deg);
transition: all 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
pointer-events: none;
}
.hub-close {
color: #111827;
}
.hub-success {
color: #ffffff;
}
/* When Radial Menu is Open */
.share-hub.is-open {
--s: 1;
}
.share-hub.is-open .hub-btn {
width: 62px;
height: 62px;
border-radius: 50%;
background: #c5bfb0;
box-shadow: 0 10px 22px rgba(0, 0, 0, 0.1);
}
.share-hub.is-open .hub-label {
opacity: 0;
transform: scale(0.5);
pointer-events: none;
}
.share-hub.is-open .hub-close {
opacity: 1;
transform: scale(1) rotate(0deg);
}
/* Copied Success State */
.share-hub.is-copied .hub-btn {
background: #334155;
box-shadow: 0 12px 28px rgba(51, 65, 85, 0.35);
}
.share-hub.is-copied .hub-close {
opacity: 0;
transform: scale(0.5) rotate(90deg);
}
.share-hub.is-copied .hub-success {
opacity: 1;
transform: scale(1) rotate(0deg);
}
/* Subtitle Status Text Below Hub */
.hub-subtitle-wrap {
position: absolute;
top: 76px;
left: 50%;
transform: translateX(-50%);
white-space: nowrap;
pointer-events: none;
}
.hub-subtitle {
font-size: 13.5px;
font-weight: 600;
color: var(--text-sub);
opacity: 0;
transform: translateY(-4px);
transition: all 0.35s ease;
display: block;
}
.share-hub.is-open .hub-subtitle {
opacity: 1;
transform: translateY(0);
}
/* Bottom Code & Trigonometry Inspector Dock */
.bottom-inspector-dock {
position: absolute;
bottom: 16px;
left: 50%;
transform: translateX(-50%);
width: 90%;
max-width: 620px;
background: rgba(15, 23, 42, 0.88);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 20px;
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.4);
z-index: 20;
overflow: hidden;
transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.bottom-inspector-dock.collapsed {
max-height: 46px;
}
.bottom-inspector-dock.collapsed .tab-panel {
display: none !important;
}
.dock-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 10px;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(0, 0, 0, 0.2);
}
.dock-tabs {
display: flex;
gap: 4px;
}
.dock-tab {
background: transparent;
border: none;
outline: none;
padding: 6px 12px;
font-size: 0.78rem;
font-weight: 700;
color: #94a3b8;
border-radius: 10px;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
}
.dock-tab:hover {
color: #ffffff;
background: rgba(255, 255, 255, 0.08);
}
.dock-tab.active {
background: rgba(255, 255, 255, 0.15);
color: #ffffff;
}
.btn-toggle-dock {
background: transparent;
border: none;
width: 32px;
height: 32px;
border-radius: 8px;
color: #94a3b8;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.85rem;
transition: all 0.2s ease;
}
.btn-toggle-dock:hover {
background: rgba(255, 255, 255, 0.1);
color: #ffffff;
}
/* Tab Panels */
.tab-panel {
display: none;
padding: 16px 20px;
}
.tab-panel.active {
display: block;
}
.trig-controls-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 16px;
}
.trig-control {
display: flex;
flex-direction: column;
gap: 6px;
}
.control-header {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.78rem;
}
.ctrl-label {
color: #94a3b8;
font-weight: 600;
}
.ctrl-val {
color: var(--indigo-primary);
font-weight: 800;
}
.range-slider {
width: 100%;
accent-color: var(--indigo-primary);
cursor: pointer;
}
.preset-btn-group {
display: flex;
gap: 4px;
}
.btn-preset {
flex: 1;
padding: 4px;
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 6px;
background: transparent;
font-size: 0.72rem;
font-weight: 700;
color: #94a3b8;
cursor: pointer;
transition: all 0.2s ease;
}
.btn-preset.active {
background: var(--indigo-primary);
color: #ffffff;
border-color: var(--indigo-primary);
}
.code-snippet {
margin: 0;
padding: 12px 14px;
background: #090a0f;
border-radius: 12px;
color: #e2e8f0;
font-family: var(--font-mono);
font-size: 0.74rem;
line-height: 1.45;
overflow-x: auto;
max-height: 140px;
}
.text-orange { color: #f97316 !important; }
.text-blue { color: #38bdf8 !important; }
.text-yellow { color: #facc15 !important; }
/* Config Modal */
.custom-modal-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(15, 23, 42, 0.6);
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
z-index: 40;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
.custom-modal-overlay.hidden {
opacity: 0;
pointer-events: none;
}
.custom-modal-dialog {
background: #1e293b;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 20px;
width: 90%;
max-width: 440px;
padding: 22px;
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);
color: #ffffff;
}
.modal-head {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.modal-head h4 {
font-size: 1.05rem;
font-weight: 800;
}
.btn-close-modal {
background: transparent;
border: none;
font-size: 1.4rem;
color: #94a3b8;
cursor: pointer;
}
.form-label {
display: block;
font-size: 0.8rem;
color: #94a3b8;
margin-bottom: 4px;
}
.form-control {
width: 100%;
padding: 10px 12px;
background: #0f172a;
border: 1px solid #334155;
border-radius: 10px;
font-size: 0.85rem;
font-family: inherit;
color: #ffffff;
outline: none;
}
.form-control:focus {
border-color: var(--indigo-primary);
}
.btn-modal-primary {
width: 100%;
padding: 12px;
border-radius: 12px;
background: var(--indigo-primary);
color: #ffffff;
font-weight: 800;
border: none;
cursor: pointer;
}
@media (max-width: 640px) {
.app-header {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.share-card-container {
max-width: 360px;
height: 360px;
}
.trig-controls-grid {
grid-template-columns: 1fr;
}
.bottom-inspector-dock {
width: 94%;
bottom: 8px;
}
}C Step 3: JavaScript Logic & Event Handling
Here is the complete JavaScript code handling the state, event listeners, mathematical logic, and dynamic DOM rendering:
(() => {
// DOM Elements
const app = document.getElementById('radialShareApp');
const confettiCanvas = document.getElementById('confettiCanvas');
const shareCard = document.getElementById('shareCard');
const shareHub = document.getElementById('shareHub');
const hubBtn = document.getElementById('hubBtn');
const hubSubtitle = document.getElementById('hubSubtitle');
const channelBtns = document.querySelectorAll('.ch');
// Controls & Modals
const btnAudioToggle = document.getElementById('btnAudioToggle');
const btnThemeToggle = document.getElementById('btnThemeToggle');
const btn3DTiltToggle = document.getElementById('btn3DTiltToggle');
const btnOpenConfig = document.getElementById('btnOpenConfig');
const configModal = document.getElementById('configModal');
const btnCloseConfig = document.getElementById('btnCloseConfig');
const btnSaveConfig = document.getElementById('btnSaveConfig');
const inputShareUrl = document.getElementById('inputShareUrl');
const inputShareTitle = document.getElementById('inputShareTitle');
// Bottom Dock & Sliders
const bottomDock = document.getElementById('bottomDock');
const btnToggleDock = document.getElementById('btnToggleDock');
const dockChevron = document.getElementById('dockChevron');
const dockTabs = document.querySelectorAll('.dock-tab');
const tabPanels = document.querySelectorAll('.tab-panel');
const sliderPitch = document.getElementById('sliderPitch');
const valPitch = document.getElementById('valPitch');
const sliderRadius = document.getElementById('sliderRadius');
const valRadius = document.getElementById('valRadius');
const presetBtns = document.querySelectorAll('.btn-preset');
const valBounciness = document.getElementById('valBounciness');
// State
let isOpen = false;
let isCopied = false;
let tilt3DEnabled = true;
let audioEnabled = true;
let audioCtx = null;
let themeIndex = 0;
const themeClasses = ['', 'theme-dark', 'theme-mint', 'theme-sunset'];
// Share Content
let shareUrl = window.location.href || 'https://example.com/share-magic';
let shareTitle = 'Check out this awesome 3D Radial Share Button animation! β¨';
// Pentatonic scale note frequencies for channel hover
const channelFreqs = {
x: 523.25, // C5
linkedin: 587.33, // D5
whatsapp: 659.25, // E5
email: 783.99, // G5
link: 880.00 // A5
};
// --- 1. Web Audio Synthesizer ---
function initAudio() {
if (!audioCtx) {
const AudioContext = window.AudioContext || window.webkitAudioContext;
audioCtx = new AudioContext();
}
if (audioCtx.state === 'suspended') {
audioCtx.resume();
}
}
function playTone(freq, dur, type = 'triangle', vol = 0.22) {
if (!audioEnabled) return;
initAudio();
if (!audioCtx) return;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = type;
osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
gain.gain.setValueAtTime(0.001, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(vol, audioCtx.currentTime + 0.015);
gain.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + dur);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + dur);
}
function playSplayChime(isOpening) {
if (!audioEnabled) return;
initAudio();
if (!audioCtx) return;
const notes = isOpening ? [440, 554.37, 659.25, 880] : [880, 659.25, 554.37, 440];
notes.forEach((f, i) => {
setTimeout(() => {
playTone(f, 0.2, 'triangle', 0.15);
}, i * 40);
});
}
function playSuccessChime() {
if (!audioEnabled) return;
initAudio();
if (!audioCtx) return;
[523.25, 659.25, 783.99, 1046.50].forEach((f, i) => {
setTimeout(() => {
playTone(f, 0.32, 'triangle', 0.22);
}, i * 65);
});
}
// --- 2. Interactive Menu Toggling ---
hubBtn.addEventListener('click', () => {
if (isCopied) {
closeMenu();
return;
}
if (isOpen) {
closeMenu();
} else {
openMenu();
}
});
function openMenu() {
isOpen = true;
isCopied = false;
shareHub.classList.remove('is-copied');
shareHub.classList.add('is-open');
hubSubtitle.innerText = 'Pick a channel';
playSplayChime(true);
}
function closeMenu() {
isOpen = false;
isCopied = false;
shareHub.classList.remove('is-open', 'is-copied');
hubSubtitle.innerText = 'Pick a channel';
playSplayChime(false);
}
// --- 3. Channel Click & Hover Interactions ---
channelBtns.forEach(btn => {
const ch = btn.dataset.ch;
btn.addEventListener('mouseenter', () => {
if (!isOpen) return;
playTone(channelFreqs[ch] || 600, 0.16, 'triangle', 0.25);
const names = {
x: 'Share to X',
linkedin: 'Share to LinkedIn',
whatsapp: 'Share to WhatsApp',
email: 'Share via Email',
link: 'Copy Page Link'
};
hubSubtitle.innerText = names[ch] || 'Pick a channel';
});
btn.addEventListener('mouseleave', () => {
if (isOpen && !isCopied) {
hubSubtitle.innerText = 'Pick a channel';
}
});
btn.addEventListener('click', (e) => {
e.stopPropagation();
executeChannelAction(ch);
});
});
function executeChannelAction(ch) {
if (ch === 'link') {
// Copy Link to Clipboard
if (navigator.clipboard) {
navigator.clipboard.writeText(shareUrl).catch(() => {});
}
isCopied = true;
shareHub.classList.add('is-copied');
hubSubtitle.innerText = 'Link copied';
playSuccessChime();
fireConfetti();
// Auto close after 2.4 seconds
setTimeout(() => {
if (isOpen && isCopied) {
closeMenu();
}
}, 2400);
} else if (ch === 'x') {
const text = encodeURIComponent(shareTitle + ' ' + shareUrl);
window.open(`https://twitter.com/intent/tweet?text=${text}`, '_blank', 'width=600,height=400');
hubSubtitle.innerText = 'Sharing to X...';
} else if (ch === 'linkedin') {
const url = encodeURIComponent(shareUrl);
window.open(`https://www.linkedin.com/sharing/share-offsite/?url=${url}`, '_blank', 'width=600,height=500');
hubSubtitle.innerText = 'Sharing to LinkedIn...';
} else if (ch === 'whatsapp') {
const text = encodeURIComponent(shareTitle + ' ' + shareUrl);
window.open(`https://api.whatsapp.com/send?text=${text}`, '_blank');
hubSubtitle.innerText = 'Opening WhatsApp...';
} else if (ch === 'email') {
const subject = encodeURIComponent(shareTitle);
const body = encodeURIComponent(shareUrl);
window.location.href = `mailto:?subject=${subject}&body=${body}`;
hubSubtitle.innerText = 'Opening Mail...';
}
}
// --- 4. Interactive 3D Card Tilt on Mouse/Touch Move ---
let targetTiltX = 0, targetTiltY = 0;
let currentTiltX = 0, currentTiltY = 0;
window.addEventListener('mousemove', (e) => {
if (!tilt3DEnabled) return;
const rect = shareCard.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const nx = (e.clientX - centerX) / (window.innerWidth * 0.5);
const ny = (e.clientY - centerY) / (window.innerHeight * 0.5);
targetTiltY = nx * 14; // rotateY degrees
targetTiltX = -ny * 14; // rotateX degrees
});
window.addEventListener('mouseleave', () => {
targetTiltX = 0;
targetTiltY = 0;
});
function render3DTilt() {
requestAnimationFrame(render3DTilt);
if (tilt3DEnabled) {
currentTiltX += (targetTiltX - currentTiltX) * 0.1;
currentTiltY += (targetTiltY - currentTiltY) * 0.1;
shareCard.style.transform = `rotateX(${currentTiltX.toFixed(2)}deg) rotateY(${currentTiltY.toFixed(2)}deg)`;
} else {
shareCard.style.transform = 'none';
}
}
render3DTilt();
// --- 5. Trigonometry Math Sliders ---
sliderPitch.addEventListener('input', (e) => {
const val = parseInt(e.target.value);
valPitch.innerHTML = `${val}°`;
app.style.setProperty('--pitch', `${val}deg`);
});
sliderRadius.addEventListener('input', (e) => {
const val = parseInt(e.target.value);
valRadius.innerText = `${val}px`;
app.style.setProperty('--r', `${val}px`);
});
presetBtns.forEach(btn => {
btn.addEventListener('click', () => {
presetBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
valBounciness.innerText = btn.innerText;
const preset = btn.dataset.preset;
if (preset === 'bouncy') {
document.documentElement.style.setProperty('--spring-ease', 'cubic-bezier(0.34, 1.56, 0.64, 1)');
} else if (preset === 'smooth') {
document.documentElement.style.setProperty('--spring-ease', 'cubic-bezier(0.25, 1, 0.5, 1)');
} else if (preset === 'snappy') {
document.documentElement.style.setProperty('--spring-ease', 'cubic-bezier(0.16, 1.3, 0.3, 1)');
}
});
});
// Dock Tabs
dockTabs.forEach(tab => {
tab.addEventListener('click', () => {
dockTabs.forEach(t => t.classList.remove('active'));
tabPanels.forEach(p => p.classList.remove('active'));
tab.classList.add('active');
const targetPanel = document.getElementById(`panel${tab.dataset.tab.charAt(0).toUpperCase() + tab.dataset.tab.slice(1)}`);
if (targetPanel) targetPanel.classList.add('active');
bottomDock.classList.remove('collapsed');
dockChevron.className = 'fas fa-chevron-down';
});
});
btnToggleDock.addEventListener('click', () => {
bottomDock.classList.toggle('collapsed');
dockChevron.className = bottomDock.classList.contains('collapsed') ? 'fas fa-chevron-up' : 'fas fa-chevron-down';
});
// Header Utility Actions
btnAudioToggle.addEventListener('click', () => {
audioEnabled = !audioEnabled;
btnAudioToggle.innerHTML = audioEnabled ? '<i class="fas fa-volume-up"></i>' : '<i class="fas fa-volume-xmark"></i>';
btnAudioToggle.style.color = audioEnabled ? '#ffffff' : '#94a3b8';
});
btnThemeToggle.addEventListener('click', () => {
themeIndex = (themeIndex + 1) % themeClasses.length;
app.className = 'radial-share-app ' + themeClasses[themeIndex];
});
btn3DTiltToggle.addEventListener('click', () => {
tilt3DEnabled = !tilt3DEnabled;
btn3DTiltToggle.style.color = tilt3DEnabled ? '#ffffff' : '#94a3b8';
});
btnOpenConfig.addEventListener('click', () => {
inputShareUrl.value = shareUrl;
inputShareTitle.value = shareTitle;
configModal.classList.remove('hidden');
});
btnCloseConfig.addEventListener('click', () => {
configModal.classList.add('hidden');
});
btnSaveConfig.addEventListener('click', () => {
shareUrl = inputShareUrl.value.trim() || shareUrl;
shareTitle = inputShareTitle.value.trim() || shareTitle;
configModal.classList.add('hidden');
});
// --- 6. Confetti Celebration Cannon ---
let confettiCtx = confettiCanvas.getContext('2d');
let confettiParticles = [];
let confettiAnimFrame = null;
function resizeConfetti() {
confettiCanvas.width = window.innerWidth;
confettiCanvas.height = window.innerHeight;
}
resizeConfetti();
window.addEventListener('resize', resizeConfetti);
function fireConfetti() {
confettiParticles = [];
const colors = ['#6366f1', '#10b981', '#3b82f6', '#ec4899', '#f59e0b', '#ffffff'];
for (let i = 0; i < 75; i++) {
confettiParticles.push({
x: window.innerWidth / 2,
y: window.innerHeight * 0.48,
vx: (Math.random() - 0.5) * 16,
vy: -Math.random() * 14 - 3,
size: Math.random() * 8 + 6,
color: colors[Math.floor(Math.random() * colors.length)],
rotation: Math.random() * Math.PI * 2,
vRot: (Math.random() - 0.5) * 0.2,
life: 1.0
});
}
if (!confettiAnimFrame) {
animateConfetti();
}
}
function animateConfetti() {
confettiCtx.clearRect(0, 0, confettiCanvas.width, confettiCanvas.height);
let activeCount = 0;
confettiParticles.forEach(p => {
if (p.life > 0) {
activeCount++;
p.x += p.vx;
p.y += p.vy;
p.vy += 0.35; // Gravity
p.rotation += p.vRot;
p.life -= 0.012;
confettiCtx.save();
confettiCtx.translate(p.x, p.y);
confettiCtx.rotate(p.rotation);
confettiCtx.fillStyle = p.color;
confettiCtx.globalAlpha = Math.max(0, p.life);
confettiCtx.fillRect(-p.size / 2, -p.size / 2, p.size, p.size * 0.6);
confettiCtx.restore();
}
});
if (activeCount > 0) {
confettiAnimFrame = requestAnimationFrame(animateConfetti);
} else {
confettiCtx.clearRect(0, 0, confettiCanvas.width, confettiCanvas.height);
confettiAnimFrame = null;
}
}
})();3. Core JavaScript Concepts You Learned
Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.
Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.
Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.
Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.
Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.
Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.
4. How to Run Locally on Your Computer
- Download the project ZIP: Click the Download Project (.ZIP) button at the top or in the live runner to save
radial-share-3d-button-project.zip. - Extract the archive: Unzip the downloaded file. Inside you will find separate
index.html,style.css,script.js,index-standalone.html, and a comprehensiveREADME.md. - Open in any browser: Simply double-click
index.html(orindex-standalone.html) to open and test immediately in Chrome, Firefox, Safari, or Edge. - Edit in Visual Studio Code: Open the extracted folder in VS Code. Right-click
index.htmland select "Open with Live Server" to enjoy instant live reloading as you modify HTML, CSS, or JS!
Bonus Challenges for Learners
Ready to level up? Try adding these extra features on your own:
- Add sound effects or audio feedback for user button clicks.
- Add custom theme color customizer with CSS variables.
- Export the results to PDF or copy as a formatted text summary.