widgies 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # Widgies
2
+
3
+ A tiny, zero-dependency set of Web Component widgets you can drop into any web page with one script tag.
4
+
5
+ - **Plain English docs**—no jargon.
6
+ - **Accessible by default**—ARIA labels, keyboard support, high-contrast, `prefers-reduced-motion`.
7
+ - **Auto light & dark themes**—switches with `prefers-color-scheme`.
8
+ - **Works everywhere**—React, Vue, Svelte, or just HTML.
9
+
10
+ ## Quick Start
11
+
12
+ ```html
13
+ <!-- 1. Import Widgies from a CDN or your build folder -->
14
+ <script type="module" src="https://unpkg.com/widgies@0.1.0/dist/widgies.min.js"></script>
15
+
16
+ <!-- 2. Optional: override design tokens -->
17
+ <style>
18
+ :root {
19
+ /* universal */
20
+ --widgies-radius: 0.5rem;
21
+ --widgies-font: 16px/1.4 Inter, sans-serif;
22
+
23
+ /* light mode */
24
+ --widgies-surface-light: #ffffffe6;
25
+ --widgies-text-light: #1a1a1a;
26
+ --widgies-accent-light: #6c4cff;
27
+
28
+ /* dark mode */
29
+ --widgies-surface-dark: #181824e6;
30
+ --widgies-text-dark: #f5f5ff;
31
+ --widgies-accent-dark: #a88bff;
32
+ }
33
+ </style>
34
+
35
+ <!-- 3. Drop a widget – no extra JS needed -->
36
+ <widgies-timer minutes="25" aria-label="25-minute timer"></widgies-timer>
37
+ ```
38
+
39
+ ## Widgets in v0.1.0
40
+
41
+ | Tag | What it does | Key attributes |
42
+ | ------------------------- | -------------------------- | ------------------- |
43
+ | `<widgies-calculator>` | 4-function calculator | – |
44
+ | `<widgies-random-number>` | Cryptographically safe RNG | `min` `max` |
45
+ | `<widgies-timer>` | Countdown / Pomodoro | `seconds` `minutes` |
46
+ | `<widgies-affirmation>` | Random positive message | `interval` (ms) |
47
+ | `<widgies-qr-code>` | QR code generator | `value` `size` |
48
+ | `<widgies-ambient>` | Rain, ocean, forest sounds | `sound` |
49
+
50
+ All widgets share **theme tokens**, **focus rings**, and **ARIA roles**. Bundle size ≈10 kB gzipped.
51
+
52
+ ## Installation
53
+
54
+ ```bash
55
+ npm install widgies
56
+ ```
57
+
58
+ ```js
59
+ // Option 1: Import all widgets (auto-registers)
60
+ import 'widgies';
61
+
62
+ // Option 2: Import individual widgets
63
+ import 'widgies/calculator';
64
+ import 'widgies/timer';
65
+ import 'widgies/random-number';
66
+ import 'widgies/affirmation';
67
+ import 'widgies/ambient';
68
+ import 'widgies/qr-code';
69
+
70
+ // Option 3: Import widget classes for manual registration
71
+ import { CalculatorWidget } from 'widgies';
72
+ CalculatorWidget.register();
73
+ ```
74
+
75
+ Or via CDN:
76
+
77
+ ```html
78
+ <script type="module" src="https://unpkg.com/widgies@latest/dist/widgies.min.js"></script>
79
+ ```
80
+
81
+ ## Development
82
+
83
+ ```bash
84
+ # Install dependencies
85
+ npm install
86
+
87
+ # Build the widget bundle
88
+ npm run build
89
+
90
+ # Generate TypeScript declarations
91
+ npm run types
92
+
93
+ # Build and watch for changes
94
+ npm run dev
95
+
96
+ # Run tests
97
+ npm test
98
+
99
+ # Full build (bundle + types)
100
+ npm run prepare
101
+ ```
102
+
103
+ ## License
104
+
105
+ MIT
@@ -0,0 +1 @@
1
+ export { AffirmationWidget, register } from './widgets/affirmation.js';
@@ -0,0 +1,129 @@
1
+ import{a as r}from"./chunk-3F3FJ7VN.js";var i=class extends r{constructor(){super(...arguments);this.display=null;this.newButton=null;this.intervalId=null;this.autoInterval=0;this.affirmations=["You are capable of amazing things.","Every challenge is an opportunity to grow.","You have the strength to overcome anything.","Your potential is limitless.","You are exactly where you need to be.","Progress, not perfection, is the goal.","You deserve good things in your life.","Your efforts are making a difference.","You are braver than you believe.","Today is full of possibilities.","You are worthy of love and respect.","Your unique perspective matters.","You have overcome challenges before.","Small steps lead to big changes.","You are learning and growing every day.","Your kindness makes the world better.","You have everything you need within you.","You are creating your own path.","Your dreams are valid and achievable.","You radiate positive energy.","You are enough, just as you are.","Every day is a fresh start.","You choose how to respond to life.","Your creativity knows no bounds.","You make a positive impact on others."];this.currentAffirmation=""}render(){this.shadow.innerHTML="";let e=this.createStyleSheet();e&&this.shadow.appendChild(e),this.autoInterval=parseInt(this.getAttribute("interval")||"0"),this.currentAffirmation=this.getRandomAffirmation();let t=document.createElement("div");t.innerHTML=`
2
+ <div class="affirmation" role="region" aria-label="Daily affirmation">
3
+ <div class="display" aria-live="polite" aria-label="Current affirmation">
4
+ ${this.currentAffirmation}
5
+ </div>
6
+ <button class="new-btn" aria-label="Get new affirmation">
7
+ \u2728 New Affirmation
8
+ </button>
9
+ ${this.autoInterval>0?'<div class="auto-indicator">Auto-refreshing</div>':""}
10
+ </div>
11
+ `,this.shadow.appendChild(t),this.display=this.shadow.querySelector(".display"),this.newButton=this.shadow.querySelector(".new-btn")}attachEventListeners(){this.newButton?.addEventListener("click",()=>{this.showNewAffirmation()}),this.addEventListener("keydown",e=>{(e.key==="Enter"||e.key===" ")&&(e.preventDefault(),this.showNewAffirmation())})}getRandomAffirmation(){let e;do{let t=Math.floor(Math.random()*this.affirmations.length);e=this.affirmations[t]}while(e===this.currentAffirmation&&this.affirmations.length>1);return e}showNewAffirmation(){this.display&&(this.display.classList.add("fade-out"),setTimeout(()=>{this.currentAffirmation=this.getRandomAffirmation(),this.display&&(this.display.textContent=this.currentAffirmation,this.display.classList.remove("fade-out"),this.display.classList.add("fade-in"),setTimeout(()=>{this.display?.classList.remove("fade-in")},300)),this.dispatchEvent(new CustomEvent("affirmation-changed",{detail:{affirmation:this.currentAffirmation},bubbles:!0}))},150))}startAutoInterval(){this.autoInterval>0&&(this.intervalId=window.setInterval(()=>{this.showNewAffirmation()},this.autoInterval))}stopAutoInterval(){this.intervalId&&(clearInterval(this.intervalId),this.intervalId=null)}static get observedAttributes(){return["interval"]}attributeChangedCallback(e,t,o){if(e==="interval"){this.stopAutoInterval(),this.autoInterval=parseInt(o||"0"),this.autoInterval>0&&this.startAutoInterval();let a=this.shadow.querySelector(".auto-indicator");a&&(a.style.display=this.autoInterval>0?"block":"none")}}connectedCallback(){super.connectedCallback(),this.startAutoInterval()}disconnectedCallback(){super.disconnectedCallback(),this.stopAutoInterval()}getStyles(){return`
12
+ ${this.getBaseStyles()}
13
+
14
+ .affirmation {
15
+ width: 350px;
16
+ text-align: center;
17
+ }
18
+
19
+ .display {
20
+ padding: 2rem 1.5rem;
21
+ margin-bottom: 1rem;
22
+ border-radius: var(--widgies-radius, 0.5rem);
23
+ background: linear-gradient(135deg,
24
+ var(--widgies-accent, var(--widgies-accent-light, #6c4cff)) 0%,
25
+ color-mix(in srgb, var(--widgies-accent, var(--widgies-accent-light, #6c4cff)) 80%, var(--widgies-surface, var(--widgies-surface-light, #ffffffe6))) 100%);
26
+ color: white;
27
+ font-size: 1.2rem;
28
+ font-weight: 500;
29
+ line-height: 1.6;
30
+ min-height: 4rem;
31
+ display: flex;
32
+ align-items: center;
33
+ justify-content: center;
34
+ text-align: center;
35
+ box-shadow: 0 4px 12px color-mix(in srgb, var(--widgies-accent, var(--widgies-accent-light, #6c4cff)) 25%, transparent);
36
+ transition: opacity 0.15s ease, transform 0.15s ease;
37
+ }
38
+
39
+ .display.fade-out {
40
+ opacity: 0;
41
+ transform: translateY(-10px);
42
+ }
43
+
44
+ .display.fade-in {
45
+ opacity: 1;
46
+ transform: translateY(0);
47
+ }
48
+
49
+ .new-btn {
50
+ width: 100%;
51
+ padding: 0.75rem 1.5rem;
52
+ font-size: 1rem;
53
+ font-weight: 600;
54
+ background: var(--widgies-surface, var(--widgies-surface-light, #ffffffe6));
55
+ color: var(--widgies-accent, var(--widgies-accent-light, #6c4cff));
56
+ border: 2px solid var(--widgies-accent, var(--widgies-accent-light, #6c4cff));
57
+ transition: all 0.2s ease;
58
+ }
59
+
60
+ .new-btn:hover {
61
+ background: var(--widgies-accent, var(--widgies-accent-light, #6c4cff));
62
+ color: white;
63
+ transform: translateY(-2px);
64
+ box-shadow: 0 4px 12px color-mix(in srgb, var(--widgies-accent, var(--widgies-accent-light, #6c4cff)) 30%, transparent);
65
+ }
66
+
67
+ .new-btn:active {
68
+ transform: translateY(0);
69
+ }
70
+
71
+ .auto-indicator {
72
+ margin-top: 0.5rem;
73
+ font-size: 0.8rem;
74
+ color: color-mix(in srgb, var(--widgies-text, var(--widgies-text-light, #1a1a1a)) 70%, transparent);
75
+ font-style: italic;
76
+ }
77
+
78
+ .auto-indicator::before {
79
+ content: "\u{1F504} ";
80
+ animation: rotate 2s linear infinite;
81
+ }
82
+
83
+ @keyframes rotate {
84
+ from { transform: rotate(0deg); }
85
+ to { transform: rotate(360deg); }
86
+ }
87
+
88
+ @media (prefers-color-scheme: dark) {
89
+ .display {
90
+ background: linear-gradient(135deg,
91
+ var(--widgies-accent, var(--widgies-accent-dark, #a88bff)) 0%,
92
+ color-mix(in srgb, var(--widgies-accent, var(--widgies-accent-dark, #a88bff)) 80%, var(--widgies-surface, var(--widgies-surface-dark, #181824e6))) 100%);
93
+ box-shadow: 0 4px 12px color-mix(in srgb, var(--widgies-accent, var(--widgies-accent-dark, #a88bff)) 25%, transparent);
94
+ }
95
+
96
+ .new-btn {
97
+ background: var(--widgies-surface, var(--widgies-surface-dark, #181824e6));
98
+ color: var(--widgies-accent, var(--widgies-accent-dark, #a88bff));
99
+ border-color: var(--widgies-accent, var(--widgies-accent-dark, #a88bff));
100
+ }
101
+
102
+ .new-btn:hover {
103
+ background: var(--widgies-accent, var(--widgies-accent-dark, #a88bff));
104
+ box-shadow: 0 4px 12px color-mix(in srgb, var(--widgies-accent, var(--widgies-accent-dark, #a88bff)) 30%, transparent);
105
+ }
106
+
107
+ .auto-indicator {
108
+ color: color-mix(in srgb, var(--widgies-text, var(--widgies-text-dark, #f5f5ff)) 70%, transparent);
109
+ }
110
+ }
111
+
112
+ @media (prefers-reduced-motion: reduce) {
113
+ .display {
114
+ transition: none;
115
+ }
116
+
117
+ .new-btn {
118
+ transition: none;
119
+ }
120
+
121
+ .new-btn:hover {
122
+ transform: none;
123
+ }
124
+
125
+ .auto-indicator::before {
126
+ animation: none;
127
+ }
128
+ }
129
+ `}};function n(){customElements.get("widgies-affirmation")||customElements.define("widgies-affirmation",i)}n();export{i as AffirmationWidget,n as register};
@@ -0,0 +1,129 @@
1
+ import{a as r}from"./chunk-BP7C5X5K.min.js";var i=class extends r{constructor(){super(...arguments);this.display=null;this.newButton=null;this.intervalId=null;this.autoInterval=0;this.affirmations=["You are capable of amazing things.","Every challenge is an opportunity to grow.","You have the strength to overcome anything.","Your potential is limitless.","You are exactly where you need to be.","Progress, not perfection, is the goal.","You deserve good things in your life.","Your efforts are making a difference.","You are braver than you believe.","Today is full of possibilities.","You are worthy of love and respect.","Your unique perspective matters.","You have overcome challenges before.","Small steps lead to big changes.","You are learning and growing every day.","Your kindness makes the world better.","You have everything you need within you.","You are creating your own path.","Your dreams are valid and achievable.","You radiate positive energy.","You are enough, just as you are.","Every day is a fresh start.","You choose how to respond to life.","Your creativity knows no bounds.","You make a positive impact on others."];this.currentAffirmation=""}render(){this.shadow.innerHTML="";let e=this.createStyleSheet();e&&this.shadow.appendChild(e),this.autoInterval=parseInt(this.getAttribute("interval")||"0"),this.currentAffirmation=this.getRandomAffirmation();let t=document.createElement("div");t.innerHTML=`
2
+ <div class="affirmation" role="region" aria-label="Daily affirmation">
3
+ <div class="display" aria-live="polite" aria-label="Current affirmation">
4
+ ${this.currentAffirmation}
5
+ </div>
6
+ <button class="new-btn" aria-label="Get new affirmation">
7
+ \u2728 New Affirmation
8
+ </button>
9
+ ${this.autoInterval>0?'<div class="auto-indicator">Auto-refreshing</div>':""}
10
+ </div>
11
+ `,this.shadow.appendChild(t),this.display=this.shadow.querySelector(".display"),this.newButton=this.shadow.querySelector(".new-btn")}attachEventListeners(){this.newButton?.addEventListener("click",()=>{this.showNewAffirmation()}),this.addEventListener("keydown",e=>{(e.key==="Enter"||e.key===" ")&&(e.preventDefault(),this.showNewAffirmation())})}getRandomAffirmation(){let e;do{let t=Math.floor(Math.random()*this.affirmations.length);e=this.affirmations[t]}while(e===this.currentAffirmation&&this.affirmations.length>1);return e}showNewAffirmation(){this.display&&(this.display.classList.add("fade-out"),setTimeout(()=>{this.currentAffirmation=this.getRandomAffirmation(),this.display&&(this.display.textContent=this.currentAffirmation,this.display.classList.remove("fade-out"),this.display.classList.add("fade-in"),setTimeout(()=>{this.display?.classList.remove("fade-in")},300)),this.dispatchEvent(new CustomEvent("affirmation-changed",{detail:{affirmation:this.currentAffirmation},bubbles:!0}))},150))}startAutoInterval(){this.autoInterval>0&&(this.intervalId=window.setInterval(()=>{this.showNewAffirmation()},this.autoInterval))}stopAutoInterval(){this.intervalId&&(clearInterval(this.intervalId),this.intervalId=null)}static get observedAttributes(){return["interval"]}attributeChangedCallback(e,t,o){if(e==="interval"){this.stopAutoInterval(),this.autoInterval=parseInt(o||"0"),this.autoInterval>0&&this.startAutoInterval();let a=this.shadow.querySelector(".auto-indicator");a&&(a.style.display=this.autoInterval>0?"block":"none")}}connectedCallback(){super.connectedCallback(),this.startAutoInterval()}disconnectedCallback(){super.disconnectedCallback(),this.stopAutoInterval()}getStyles(){return`
12
+ ${this.getBaseStyles()}
13
+
14
+ .affirmation {
15
+ width: 350px;
16
+ text-align: center;
17
+ }
18
+
19
+ .display {
20
+ padding: 2rem 1.5rem;
21
+ margin-bottom: 1rem;
22
+ border-radius: var(--widgies-radius, 0.5rem);
23
+ background: linear-gradient(135deg,
24
+ var(--widgies-accent, var(--widgies-accent-light, #6c4cff)) 0%,
25
+ color-mix(in srgb, var(--widgies-accent, var(--widgies-accent-light, #6c4cff)) 80%, var(--widgies-surface, var(--widgies-surface-light, #ffffffe6))) 100%);
26
+ color: white;
27
+ font-size: 1.2rem;
28
+ font-weight: 500;
29
+ line-height: 1.6;
30
+ min-height: 4rem;
31
+ display: flex;
32
+ align-items: center;
33
+ justify-content: center;
34
+ text-align: center;
35
+ box-shadow: 0 4px 12px color-mix(in srgb, var(--widgies-accent, var(--widgies-accent-light, #6c4cff)) 25%, transparent);
36
+ transition: opacity 0.15s ease, transform 0.15s ease;
37
+ }
38
+
39
+ .display.fade-out {
40
+ opacity: 0;
41
+ transform: translateY(-10px);
42
+ }
43
+
44
+ .display.fade-in {
45
+ opacity: 1;
46
+ transform: translateY(0);
47
+ }
48
+
49
+ .new-btn {
50
+ width: 100%;
51
+ padding: 0.75rem 1.5rem;
52
+ font-size: 1rem;
53
+ font-weight: 600;
54
+ background: var(--widgies-surface, var(--widgies-surface-light, #ffffffe6));
55
+ color: var(--widgies-accent, var(--widgies-accent-light, #6c4cff));
56
+ border: 2px solid var(--widgies-accent, var(--widgies-accent-light, #6c4cff));
57
+ transition: all 0.2s ease;
58
+ }
59
+
60
+ .new-btn:hover {
61
+ background: var(--widgies-accent, var(--widgies-accent-light, #6c4cff));
62
+ color: white;
63
+ transform: translateY(-2px);
64
+ box-shadow: 0 4px 12px color-mix(in srgb, var(--widgies-accent, var(--widgies-accent-light, #6c4cff)) 30%, transparent);
65
+ }
66
+
67
+ .new-btn:active {
68
+ transform: translateY(0);
69
+ }
70
+
71
+ .auto-indicator {
72
+ margin-top: 0.5rem;
73
+ font-size: 0.8rem;
74
+ color: color-mix(in srgb, var(--widgies-text, var(--widgies-text-light, #1a1a1a)) 70%, transparent);
75
+ font-style: italic;
76
+ }
77
+
78
+ .auto-indicator::before {
79
+ content: "\u{1F504} ";
80
+ animation: rotate 2s linear infinite;
81
+ }
82
+
83
+ @keyframes rotate {
84
+ from { transform: rotate(0deg); }
85
+ to { transform: rotate(360deg); }
86
+ }
87
+
88
+ @media (prefers-color-scheme: dark) {
89
+ .display {
90
+ background: linear-gradient(135deg,
91
+ var(--widgies-accent, var(--widgies-accent-dark, #a88bff)) 0%,
92
+ color-mix(in srgb, var(--widgies-accent, var(--widgies-accent-dark, #a88bff)) 80%, var(--widgies-surface, var(--widgies-surface-dark, #181824e6))) 100%);
93
+ box-shadow: 0 4px 12px color-mix(in srgb, var(--widgies-accent, var(--widgies-accent-dark, #a88bff)) 25%, transparent);
94
+ }
95
+
96
+ .new-btn {
97
+ background: var(--widgies-surface, var(--widgies-surface-dark, #181824e6));
98
+ color: var(--widgies-accent, var(--widgies-accent-dark, #a88bff));
99
+ border-color: var(--widgies-accent, var(--widgies-accent-dark, #a88bff));
100
+ }
101
+
102
+ .new-btn:hover {
103
+ background: var(--widgies-accent, var(--widgies-accent-dark, #a88bff));
104
+ box-shadow: 0 4px 12px color-mix(in srgb, var(--widgies-accent, var(--widgies-accent-dark, #a88bff)) 30%, transparent);
105
+ }
106
+
107
+ .auto-indicator {
108
+ color: color-mix(in srgb, var(--widgies-text, var(--widgies-text-dark, #f5f5ff)) 70%, transparent);
109
+ }
110
+ }
111
+
112
+ @media (prefers-reduced-motion: reduce) {
113
+ .display {
114
+ transition: none;
115
+ }
116
+
117
+ .new-btn {
118
+ transition: none;
119
+ }
120
+
121
+ .new-btn:hover {
122
+ transform: none;
123
+ }
124
+
125
+ .auto-indicator::before {
126
+ animation: none;
127
+ }
128
+ }
129
+ `}};function n(){customElements.get("widgies-affirmation")||customElements.define("widgies-affirmation",i)}n();export{i as AffirmationWidget,n as register};
@@ -0,0 +1 @@
1
+ export { AmbientWidget, register } from './widgets/ambient.js';
@@ -0,0 +1,191 @@
1
+ import{a as o}from"./chunk-3F3FJ7VN.js";var n=class extends o{constructor(){super(...arguments);this.audioContext=null;this.oscillators=[];this.gainNodes=[];this.gainNode=null;this.isPlaying=!1;this.currentSound="rain";this.playButton=null;this.soundSelect=null;this.volumeSlider=null;this.sounds={rain:{name:"Rain",icon:"\u{1F327}\uFE0F"},ocean:{name:"Ocean Waves",icon:"\u{1F30A}"},forest:{name:"Forest",icon:"\u{1F332}"},river:{name:"River",icon:"\u{1F3DE}\uFE0F"},campfire:{name:"Crackling Campfire",icon:"\u{1F525}"}}}render(){this.shadow.innerHTML="";let t=this.createStyleSheet();t&&this.shadow.appendChild(t);let e=this.getAttribute("sound")||"rain";e in this.sounds&&(this.currentSound=e);let i=document.createElement("div");i.innerHTML=`
2
+ <div class="ambient" role="application" aria-label="Ambient sound player">
3
+ <div class="sound-display">
4
+ <span class="sound-icon">${this.sounds[this.currentSound].icon}</span>
5
+ <span class="sound-name">${this.sounds[this.currentSound].name}</span>
6
+ </div>
7
+
8
+ <div class="controls">
9
+ <select class="sound-select" aria-label="Choose ambient sound">
10
+ ${Object.entries(this.sounds).map(([a,s])=>`<option value="${a}" ${a===this.currentSound?"selected":""}>${s.icon} ${s.name}</option>`).join("")}
11
+ </select>
12
+
13
+ <button class="play-btn" aria-label="Play or pause ambient sound">
14
+ <span class="play-icon">\u25B6</span>
15
+ <span class="pause-icon" style="display: none">\u23F8</span>
16
+ </button>
17
+ </div>
18
+
19
+ <div class="volume-control">
20
+ <label>
21
+ <span class="volume-label">\u{1F50A} Volume</span>
22
+ <input type="range" class="volume-slider" min="0" max="100" value="50" aria-label="Volume control">
23
+ </label>
24
+ </div>
25
+
26
+ <div class="status" aria-live="polite" aria-label="Player status">
27
+ Ready to play
28
+ </div>
29
+ </div>
30
+ `,this.shadow.appendChild(i),this.playButton=this.shadow.querySelector(".play-btn"),this.soundSelect=this.shadow.querySelector(".sound-select"),this.volumeSlider=this.shadow.querySelector(".volume-slider")}attachEventListeners(){this.playButton?.addEventListener("click",()=>{this.togglePlayback()}),this.soundSelect?.addEventListener("change",()=>{this.changeSound(this.soundSelect.value)}),this.volumeSlider?.addEventListener("input",()=>{this.updateVolume()}),this.addEventListener("keydown",t=>{t.key===" "&&(t.preventDefault(),this.togglePlayback())})}async initAudioContext(){this.audioContext||(this.audioContext=new(window.AudioContext||window.webkitAudioContext)),this.audioContext.state==="suspended"&&await this.audioContext.resume(),this.gainNode||(this.gainNode=this.audioContext.createGain(),this.gainNode.connect(this.audioContext.destination))}createWhiteNoise(){if(!this.audioContext)throw new Error("AudioContext not initialized");let t=this.audioContext.sampleRate*2,e=this.audioContext.createBuffer(1,t,this.audioContext.sampleRate),i=e.getChannelData(0);for(let s=0;s<t;s++)i[s]=Math.random()*2-1;let a=this.audioContext.createBufferSource();return a.buffer=e,a.loop=!0,a}createRainSound(){let t=this.createWhiteNoise(),e=this.audioContext.createBiquadFilter();return e.type="lowpass",e.frequency.value=1e3,t.connect(e),e.connect(this.gainNode),t}createOceanSound(){let t=this.createWhiteNoise(),e=this.audioContext.createBiquadFilter();e.type="lowpass",e.frequency.value=400;let i=this.audioContext.createOscillator(),a=this.audioContext.createGain();return i.frequency.value=.1,a.gain.value=200,i.connect(a),a.connect(e.frequency),t.connect(e),e.connect(this.gainNode),i.start(),t}createForestSound(){let t=this.createWhiteNoise(),e=this.audioContext.createBiquadFilter();return e.type="bandpass",e.frequency.value=800,e.Q.value=.5,t.connect(e),e.connect(this.gainNode),t}createCampfireSound(){let t=this.createWhiteNoise(),e=this.audioContext.createBiquadFilter();return e.type="lowpass",e.frequency.value=300,t.connect(e),e.connect(this.gainNode),t}async togglePlayback(){try{await this.initAudioContext(),this.isPlaying?this.stopSound():await this.playSound()}catch{this.updateStatus("Audio not available")}}async playSound(){if(!this.audioContext||!this.gainNode)return;let t=this.sounds[this.currentSound],e=parseInt(this.volumeSlider?.value||"50")/100;this.gainNode.gain.setValueAtTime(e*.3,this.audioContext.currentTime);let i;switch(this.currentSound){case"rain":i=this.createRainSound();break;case"ocean":i=this.createOceanSound();break;case"forest":i=this.createForestSound();break;case"campfire":i=this.createCampfireSound();break;case"river":i=this.createForestSound();break;default:i=this.createRainSound()}i.start(),this.oscillators=[i],this.isPlaying=!0,this.updatePlayButton(),this.updateStatus(`Playing ${t.name}`),this.dispatchEvent(new CustomEvent("ambient-started",{detail:{sound:this.currentSound,volume:e},bubbles:!0}))}stopSound(){this.oscillators.forEach(t=>{this.audioContext&&t.stop&&t.stop(this.audioContext.currentTime+.1)}),this.gainNode&&this.audioContext&&this.gainNode.gain.linearRampToValueAtTime(0,this.audioContext.currentTime+.1),this.oscillators=[],this.gainNodes=[],this.isPlaying=!1,this.updatePlayButton(),this.updateStatus("Stopped"),this.dispatchEvent(new CustomEvent("ambient-stopped",{detail:{sound:this.currentSound},bubbles:!0}))}changeSound(t){let e=this.isPlaying;this.isPlaying&&this.stopSound(),this.currentSound=t,this.updateSoundDisplay(),e&&setTimeout(()=>this.playSound(),200),this.dispatchEvent(new CustomEvent("ambient-changed",{detail:{sound:this.currentSound},bubbles:!0}))}updateVolume(){if(!this.volumeSlider||!this.isPlaying||!this.gainNode||!this.audioContext)return;let t=parseInt(this.volumeSlider.value)/100;this.gainNode.gain.linearRampToValueAtTime(t*.3,this.audioContext.currentTime+.1)}updatePlayButton(){let t=this.shadow.querySelector(".play-icon"),e=this.shadow.querySelector(".pause-icon");t&&e&&(this.isPlaying?(t.style.display="none",e.style.display="inline"):(t.style.display="inline",e.style.display="none"))}updateSoundDisplay(){let t=this.shadow.querySelector(".sound-icon"),e=this.shadow.querySelector(".sound-name"),i=this.sounds[this.currentSound];t&&(t.textContent=i.icon),e&&(e.textContent=i.name)}updateStatus(t){let e=this.shadow.querySelector(".status");e&&(e.textContent=t)}static get observedAttributes(){return["sound"]}attributeChangedCallback(t,e,i){t==="sound"&&i&&i in this.sounds&&(this.changeSound(i),this.soundSelect&&(this.soundSelect.value=i))}disconnectedCallback(){super.disconnectedCallback(),this.isPlaying&&this.stopSound(),this.audioContext&&this.audioContext.close()}getStyles(){return`
31
+ ${this.getBaseStyles()}
32
+
33
+ .ambient {
34
+ width: 300px;
35
+ text-align: center;
36
+ }
37
+
38
+ .sound-display {
39
+ display: flex;
40
+ align-items: center;
41
+ justify-content: center;
42
+ gap: 0.75rem;
43
+ margin-bottom: 1.5rem;
44
+ padding: 1rem;
45
+ background: linear-gradient(135deg,
46
+ var(--widgies-accent, var(--widgies-accent-light, #6c4cff)) 10%,
47
+ transparent 50%);
48
+ border-radius: var(--widgies-radius, 0.5rem);
49
+ border: 1px solid color-mix(in srgb, var(--widgies-accent, var(--widgies-accent-light, #6c4cff)) 30%, transparent);
50
+ }
51
+
52
+ .sound-icon {
53
+ font-size: 2rem;
54
+ }
55
+
56
+ .sound-name {
57
+ font-size: 1.2rem;
58
+ font-weight: 600;
59
+ color: var(--widgies-accent, var(--widgies-accent-light, #6c4cff));
60
+ }
61
+
62
+ .controls {
63
+ display: flex;
64
+ gap: 1rem;
65
+ margin-bottom: 1.5rem;
66
+ align-items: center;
67
+ }
68
+
69
+ .sound-select {
70
+ flex: 1;
71
+ padding: 0.75rem;
72
+ border: 1px solid color-mix(in srgb, var(--widgies-text, var(--widgies-text-light, #1a1a1a)) 30%, transparent);
73
+ border-radius: var(--widgies-radius, 0.5rem);
74
+ background: var(--widgies-surface, var(--widgies-surface-light, #ffffffe6));
75
+ color: var(--widgies-text, var(--widgies-text-light, #1a1a1a));
76
+ font: inherit;
77
+ }
78
+
79
+ .sound-select:focus {
80
+ outline: 2px solid var(--widgies-accent, var(--widgies-accent-light, #6c4cff));
81
+ outline-offset: 1px;
82
+ }
83
+
84
+ .play-btn {
85
+ width: 60px;
86
+ height: 60px;
87
+ border-radius: 50%;
88
+ font-size: 1.5rem;
89
+ display: flex;
90
+ align-items: center;
91
+ justify-content: center;
92
+ padding: 0;
93
+ box-shadow: 0 4px 12px color-mix(in srgb, var(--widgies-accent, var(--widgies-accent-light, #6c4cff)) 25%, transparent);
94
+ }
95
+
96
+ .volume-control {
97
+ margin-bottom: 1rem;
98
+ }
99
+
100
+ .volume-control label {
101
+ display: flex;
102
+ flex-direction: column;
103
+ gap: 0.5rem;
104
+ align-items: center;
105
+ }
106
+
107
+ .volume-label {
108
+ font-size: 0.9rem;
109
+ font-weight: 500;
110
+ }
111
+
112
+ .volume-slider {
113
+ width: 100%;
114
+ height: 8px;
115
+ border-radius: 4px;
116
+ background: color-mix(in srgb, var(--widgies-text, var(--widgies-text-light, #1a1a1a)) 20%, transparent);
117
+ outline: none;
118
+ -webkit-appearance: none;
119
+ }
120
+
121
+ .volume-slider::-webkit-slider-thumb {
122
+ -webkit-appearance: none;
123
+ width: 20px;
124
+ height: 20px;
125
+ border-radius: 50%;
126
+ background: var(--widgies-accent, var(--widgies-accent-light, #6c4cff));
127
+ cursor: pointer;
128
+ box-shadow: 0 2px 6px color-mix(in srgb, var(--widgies-accent, var(--widgies-accent-light, #6c4cff)) 40%, transparent);
129
+ }
130
+
131
+ .volume-slider::-moz-range-thumb {
132
+ width: 20px;
133
+ height: 20px;
134
+ border-radius: 50%;
135
+ background: var(--widgies-accent, var(--widgies-accent-light, #6c4cff));
136
+ cursor: pointer;
137
+ border: none;
138
+ box-shadow: 0 2px 6px color-mix(in srgb, var(--widgies-accent, var(--widgies-accent-light, #6c4cff)) 40%, transparent);
139
+ }
140
+
141
+ .status {
142
+ font-size: 0.9rem;
143
+ color: color-mix(in srgb, var(--widgies-text, var(--widgies-text-light, #1a1a1a)) 70%, transparent);
144
+ font-style: italic;
145
+ }
146
+
147
+ @media (prefers-color-scheme: dark) {
148
+ .sound-display {
149
+ background: linear-gradient(135deg,
150
+ var(--widgies-accent, var(--widgies-accent-dark, #a88bff)) 10%,
151
+ transparent 50%);
152
+ border-color: color-mix(in srgb, var(--widgies-accent, var(--widgies-accent-dark, #a88bff)) 30%, transparent);
153
+ }
154
+
155
+ .sound-name {
156
+ color: var(--widgies-accent, var(--widgies-accent-dark, #a88bff));
157
+ }
158
+
159
+ .sound-select {
160
+ background: var(--widgies-surface, var(--widgies-surface-dark, #181824e6));
161
+ color: var(--widgies-text, var(--widgies-text-dark, #f5f5ff));
162
+ border-color: color-mix(in srgb, var(--widgies-text, var(--widgies-text-dark, #f5f5ff)) 30%, transparent);
163
+ }
164
+
165
+ .sound-select:focus {
166
+ outline-color: var(--widgies-accent, var(--widgies-accent-dark, #a88bff));
167
+ }
168
+
169
+ .play-btn {
170
+ box-shadow: 0 4px 12px color-mix(in srgb, var(--widgies-accent, var(--widgies-accent-dark, #a88bff)) 25%, transparent);
171
+ }
172
+
173
+ .volume-slider {
174
+ background: color-mix(in srgb, var(--widgies-text, var(--widgies-text-dark, #f5f5ff)) 20%, transparent);
175
+ }
176
+
177
+ .volume-slider::-webkit-slider-thumb {
178
+ background: var(--widgies-accent, var(--widgies-accent-dark, #a88bff));
179
+ box-shadow: 0 2px 6px color-mix(in srgb, var(--widgies-accent, var(--widgies-accent-dark, #a88bff)) 40%, transparent);
180
+ }
181
+
182
+ .volume-slider::-moz-range-thumb {
183
+ background: var(--widgies-accent, var(--widgies-accent-dark, #a88bff));
184
+ box-shadow: 0 2px 6px color-mix(in srgb, var(--widgies-accent, var(--widgies-accent-dark, #a88bff)) 40%, transparent);
185
+ }
186
+
187
+ .status {
188
+ color: color-mix(in srgb, var(--widgies-text, var(--widgies-text-dark, #f5f5ff)) 70%, transparent);
189
+ }
190
+ }
191
+ `}};function r(){customElements.get("widgies-ambient")||customElements.define("widgies-ambient",n)}r();export{n as AmbientWidget,r as register};