5 Commits

Author SHA1 Message Date
Idrees Hassan
47b418324c Fix menu item text not working at first 2026-01-04 18:08:26 -05:00
Idrees Hassan
e5956426d5 Add toggle to enable/disable sound 2026-01-04 18:02:47 -05:00
Idrees Hassan
0cc06a8856 Change sound timings 2026-01-04 17:39:38 -05:00
Idrees Hassan
dd4184f642 Refine chirp sound 2026-01-04 17:38:14 -05:00
Idrees Hassan
5a82ba858f Add chirping when pet 2026-01-04 17:34:34 -05:00
12 changed files with 387 additions and 57 deletions

BIN
dist/extension.zip vendored

Binary file not shown.

View File

@@ -857,6 +857,50 @@
} }
} }
// @ts-check
class Birdsong {
/**
* @type {AudioContext}
*/
audioContext;
chirp() {
if (!this.audioContext) {
this.audioContext = new AudioContext();
}
const TIMES = [0, 0.06, 0.10, 0.15];
const FREQUENCIES = [2200,
3500 + Math.random() * 600,
2100 + Math.random() * 200,
1600 + Math.random() * 400];
const VOLUMES = [0.0001, 0.3, 0.3, 0.0001];
const oscillator = this.audioContext.createOscillator();
oscillator.type = "sine";
const gain = this.audioContext.createGain();
oscillator.connect(gain);
gain.connect(this.audioContext.destination);
const now = this.audioContext.currentTime;
for (let i = 0; i < TIMES.length; i++) {
const time = TIMES[i] + now;
if (i === 0) {
oscillator.frequency.setValueAtTime(FREQUENCIES[i], time);
gain.gain.setValueAtTime(VOLUMES[i], time);
} else {
oscillator.frequency.exponentialRampToValueAtTime(FREQUENCIES[i], time);
gain.gain.exponentialRampToValueAtTime(VOLUMES[i], time);
}
}
oscillator.start(now);
oscillator.stop(now + TIMES[TIMES.length - 1]);
}
}
const SAVE_KEY = "birbSaveData"; const SAVE_KEY = "birbSaveData";
/** /**
@@ -1165,7 +1209,7 @@
class MenuItem { class MenuItem {
/** /**
* @param {string} text * @param {string|(() => string)} text
* @param {() => void} action * @param {() => void} action
* @param {boolean} [removeMenu] * @param {boolean} [removeMenu]
*/ */
@@ -1214,7 +1258,7 @@
if (item instanceof Separator) { if (item instanceof Separator) {
return makeElement("birb-window-separator"); return makeElement("birb-window-separator");
} }
let menuItem = makeElement("birb-menu-item", item.text); let menuItem = makeElement("birb-menu-item", typeof item.text === "function" ? item.text() : item.text);
onClick(menuItem, () => { onClick(menuItem, () => {
if (item.removeMenu) { if (item.removeMenu) {
removeMenuCallback(); removeMenuCallback();
@@ -1321,7 +1365,8 @@
* @typedef {typeof DEFAULT_SETTINGS} Settings * @typedef {typeof DEFAULT_SETTINGS} Settings
*/ */
const DEFAULT_SETTINGS = { const DEFAULT_SETTINGS = {
birbMode: false birbMode: false,
soundEnabled: true
}; };
// Rendering constants // Rendering constants
@@ -1534,6 +1579,7 @@
.birb-menu-item { .birb-menu-item {
width: calc(100% - var(--birb-double-border-size)); width: calc(100% - var(--birb-double-border-size));
white-space: nowrap;
font-size: 14px; font-size: 14px;
padding-top: 4px; padding-top: 4px;
padding-bottom: 4px; padding-bottom: 4px;
@@ -1806,12 +1852,16 @@
const settingsItems = [ const settingsItems = [
new MenuItem("Go Back", () => switchMenuItems(menuItems, updateMenuLocation), false), new MenuItem("Go Back", () => switchMenuItems(menuItems, updateMenuLocation), false),
new Separator(), new Separator(),
new MenuItem("Toggle Birb Mode", () => { new MenuItem(() => `${settings().soundEnabled ? "Disable" : "Enable"} Sound`, () => {
userSettings.birbMode = !userSettings.birbMode; userSettings.soundEnabled = !settings().soundEnabled;
save();
}),
new MenuItem(() => `Toggle ${birdBirb(true)} Mode`, () => {
userSettings.birbMode = !settings().birbMode;
save(); save();
const message = makeElement("birb-message-content"); const message = makeElement("birb-message-content");
message.appendChild(document.createTextNode(`Your ${birdBirb().toLowerCase()} shall now be referred to as "${birdBirb()}"`)); message.appendChild(document.createTextNode(`Your ${birdBirb().toLowerCase()} shall now be referred to as "${birdBirb()}"`));
if (userSettings.birbMode) { if (settings().birbMode) {
message.appendChild(document.createElement("br")); message.appendChild(document.createElement("br"));
message.appendChild(document.createElement("br")); message.appendChild(document.createElement("br"));
message.appendChild(document.createTextNode("Welcome back to 2012")); message.appendChild(document.createTextNode("Welcome back to 2012"));
@@ -1819,7 +1869,7 @@
insertModal(`${birdBirb()} Mode`, message); insertModal(`${birdBirb()} Mode`, message);
}), }),
new Separator(), new Separator(),
new MenuItem("2026.1.1", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.1.1"); }, false), new MenuItem("2026.1.4", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.1.4"); }, false),
]; ];
const styleElement = document.createElement("style"); const styleElement = document.createElement("style");
@@ -1833,6 +1883,8 @@
FLYING: "flying", FLYING: "flying",
}; };
const birdsong = new Birdsong();
let frozen = false; let frozen = false;
let stateStart = Date.now(); let stateStart = Date.now();
let currentState = States.IDLE; let currentState = States.IDLE;
@@ -1923,8 +1975,8 @@
/** /**
* Bird or birb, you decide * Bird or birb, you decide
*/ */
function birdBirb() { function birdBirb(invert = false) {
return settings().birbMode ? "Birb" : "Bird"; return settings().birbMode !== invert ? "Birb" : "Bird";
} }
function init() { function init() {
@@ -2513,6 +2565,9 @@
function pet() { function pet() {
if (currentState === States.IDLE && birb.getCurrentAnimation() !== Animations.HEART) { if (currentState === States.IDLE && birb.getCurrentAnimation() !== Animations.HEART) {
if (settings().soundEnabled) {
birdsong.chirp();
}
birb.setAnimation(Animations.HEART); birb.setAnimation(Animations.HEART);
lastPetTimestamp = Date.now(); lastPetTimestamp = Date.now();
} }

View File

@@ -2,7 +2,7 @@
"manifest_version": 3, "manifest_version": 3,
"name": "Pocket Bird", "name": "Pocket Bird",
"description": "It's a pet bird in your browser, what more could you want?", "description": "It's a pet bird in your browser, what more could you want?",
"version": "2026.1.1", "version": "2026.1.4",
"homepage_url": "https://idreesinc.com", "homepage_url": "https://idreesinc.com",
"icons": { "icons": {
"48": "images/icons/transparent/48x48x1.png", "48": "images/icons/transparent/48x48x1.png",

75
dist/obsidian/main.js vendored
View File

@@ -1,7 +1,7 @@
const { Plugin, Notice } = require('obsidian'); const { Plugin, Notice } = require('obsidian');
module.exports = class PocketBird extends Plugin { module.exports = class PocketBird extends Plugin {
onload() { onload() {
console.log("Loading Pocket Bird version 2026.1.1..."); console.log("Loading Pocket Bird version 2026.1.4...");
const OBSIDIAN_PLUGIN = this; const OBSIDIAN_PLUGIN = this;
(function () { (function () {
'use strict'; 'use strict';
@@ -862,6 +862,50 @@ module.exports = class PocketBird extends Plugin {
} }
} }
// @ts-check
class Birdsong {
/**
* @type {AudioContext}
*/
audioContext;
chirp() {
if (!this.audioContext) {
this.audioContext = new AudioContext();
}
const TIMES = [0, 0.06, 0.10, 0.15];
const FREQUENCIES = [2200,
3500 + Math.random() * 600,
2100 + Math.random() * 200,
1600 + Math.random() * 400];
const VOLUMES = [0.0001, 0.3, 0.3, 0.0001];
const oscillator = this.audioContext.createOscillator();
oscillator.type = "sine";
const gain = this.audioContext.createGain();
oscillator.connect(gain);
gain.connect(this.audioContext.destination);
const now = this.audioContext.currentTime;
for (let i = 0; i < TIMES.length; i++) {
const time = TIMES[i] + now;
if (i === 0) {
oscillator.frequency.setValueAtTime(FREQUENCIES[i], time);
gain.gain.setValueAtTime(VOLUMES[i], time);
} else {
oscillator.frequency.exponentialRampToValueAtTime(FREQUENCIES[i], time);
gain.gain.exponentialRampToValueAtTime(VOLUMES[i], time);
}
}
oscillator.start(now);
oscillator.stop(now + TIMES[TIMES.length - 1]);
}
}
const ROOT_PATH = ""; const ROOT_PATH = "";
/** /**
@@ -1208,7 +1252,7 @@ module.exports = class PocketBird extends Plugin {
class MenuItem { class MenuItem {
/** /**
* @param {string} text * @param {string|(() => string)} text
* @param {() => void} action * @param {() => void} action
* @param {boolean} [removeMenu] * @param {boolean} [removeMenu]
*/ */
@@ -1257,7 +1301,7 @@ module.exports = class PocketBird extends Plugin {
if (item instanceof Separator) { if (item instanceof Separator) {
return makeElement("birb-window-separator"); return makeElement("birb-window-separator");
} }
let menuItem = makeElement("birb-menu-item", item.text); let menuItem = makeElement("birb-menu-item", typeof item.text === "function" ? item.text() : item.text);
onClick(menuItem, () => { onClick(menuItem, () => {
if (item.removeMenu) { if (item.removeMenu) {
removeMenuCallback(); removeMenuCallback();
@@ -1364,7 +1408,8 @@ module.exports = class PocketBird extends Plugin {
* @typedef {typeof DEFAULT_SETTINGS} Settings * @typedef {typeof DEFAULT_SETTINGS} Settings
*/ */
const DEFAULT_SETTINGS = { const DEFAULT_SETTINGS = {
birbMode: false birbMode: false,
soundEnabled: true
}; };
// Rendering constants // Rendering constants
@@ -1577,6 +1622,7 @@ module.exports = class PocketBird extends Plugin {
.birb-menu-item { .birb-menu-item {
width: calc(100% - var(--birb-double-border-size)); width: calc(100% - var(--birb-double-border-size));
white-space: nowrap;
font-size: 14px; font-size: 14px;
padding-top: 4px; padding-top: 4px;
padding-bottom: 4px; padding-bottom: 4px;
@@ -1849,12 +1895,16 @@ module.exports = class PocketBird extends Plugin {
const settingsItems = [ const settingsItems = [
new MenuItem("Go Back", () => switchMenuItems(menuItems, updateMenuLocation), false), new MenuItem("Go Back", () => switchMenuItems(menuItems, updateMenuLocation), false),
new Separator(), new Separator(),
new MenuItem("Toggle Birb Mode", () => { new MenuItem(() => `${settings().soundEnabled ? "Disable" : "Enable"} Sound`, () => {
userSettings.birbMode = !userSettings.birbMode; userSettings.soundEnabled = !settings().soundEnabled;
save();
}),
new MenuItem(() => `Toggle ${birdBirb(true)} Mode`, () => {
userSettings.birbMode = !settings().birbMode;
save(); save();
const message = makeElement("birb-message-content"); const message = makeElement("birb-message-content");
message.appendChild(document.createTextNode(`Your ${birdBirb().toLowerCase()} shall now be referred to as "${birdBirb()}"`)); message.appendChild(document.createTextNode(`Your ${birdBirb().toLowerCase()} shall now be referred to as "${birdBirb()}"`));
if (userSettings.birbMode) { if (settings().birbMode) {
message.appendChild(document.createElement("br")); message.appendChild(document.createElement("br"));
message.appendChild(document.createElement("br")); message.appendChild(document.createElement("br"));
message.appendChild(document.createTextNode("Welcome back to 2012")); message.appendChild(document.createTextNode("Welcome back to 2012"));
@@ -1862,7 +1912,7 @@ module.exports = class PocketBird extends Plugin {
insertModal(`${birdBirb()} Mode`, message); insertModal(`${birdBirb()} Mode`, message);
}), }),
new Separator(), new Separator(),
new MenuItem("2026.1.1", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.1.1"); }, false), new MenuItem("2026.1.4", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.1.4"); }, false),
]; ];
const styleElement = document.createElement("style"); const styleElement = document.createElement("style");
@@ -1876,6 +1926,8 @@ module.exports = class PocketBird extends Plugin {
FLYING: "flying", FLYING: "flying",
}; };
const birdsong = new Birdsong();
let frozen = false; let frozen = false;
let stateStart = Date.now(); let stateStart = Date.now();
let currentState = States.IDLE; let currentState = States.IDLE;
@@ -1966,8 +2018,8 @@ module.exports = class PocketBird extends Plugin {
/** /**
* Bird or birb, you decide * Bird or birb, you decide
*/ */
function birdBirb() { function birdBirb(invert = false) {
return settings().birbMode ? "Birb" : "Bird"; return settings().birbMode !== invert ? "Birb" : "Bird";
} }
function init() { function init() {
@@ -2556,6 +2608,9 @@ module.exports = class PocketBird extends Plugin {
function pet() { function pet() {
if (currentState === States.IDLE && birb.getCurrentAnimation() !== Animations.HEART) { if (currentState === States.IDLE && birb.getCurrentAnimation() !== Animations.HEART) {
if (settings().soundEnabled) {
birdsong.chirp();
}
birb.setAnimation(Animations.HEART); birb.setAnimation(Animations.HEART);
lastPetTimestamp = Date.now(); lastPetTimestamp = Date.now();
} }

View File

@@ -1,7 +1,7 @@
{ {
"id": "pocket-bird", "id": "pocket-bird",
"name": "Pocket Bird", "name": "Pocket Bird",
"version": "2026.1.1", "version": "2026.1.4",
"minAppVersion": "0.15.0", "minAppVersion": "0.15.0",
"description": "Add a pet bird to fly around your notes and keep you company!", "description": "Add a pet bird to fly around your notes and keep you company!",
"author": "Idrees Hassan", "author": "Idrees Hassan",

View File

@@ -1,7 +1,7 @@
// ==UserScript== // ==UserScript==
// @name Pocket Bird // @name Pocket Bird
// @namespace https://idreesinc.com // @namespace https://idreesinc.com
// @version 2026.1.1 // @version 2026.1.4
// @description It's a pet bird in your browser, what more could you want? // @description It's a pet bird in your browser, what more could you want?
// @author Idrees // @author Idrees
// @downloadURL https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/userscript/birb.user.js // @downloadURL https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/userscript/birb.user.js
@@ -871,6 +871,50 @@
} }
} }
// @ts-check
class Birdsong {
/**
* @type {AudioContext}
*/
audioContext;
chirp() {
if (!this.audioContext) {
this.audioContext = new AudioContext();
}
const TIMES = [0, 0.06, 0.10, 0.15];
const FREQUENCIES = [2200,
3500 + Math.random() * 600,
2100 + Math.random() * 200,
1600 + Math.random() * 400];
const VOLUMES = [0.0001, 0.3, 0.3, 0.0001];
const oscillator = this.audioContext.createOscillator();
oscillator.type = "sine";
const gain = this.audioContext.createGain();
oscillator.connect(gain);
gain.connect(this.audioContext.destination);
const now = this.audioContext.currentTime;
for (let i = 0; i < TIMES.length; i++) {
const time = TIMES[i] + now;
if (i === 0) {
oscillator.frequency.setValueAtTime(FREQUENCIES[i], time);
gain.gain.setValueAtTime(VOLUMES[i], time);
} else {
oscillator.frequency.exponentialRampToValueAtTime(FREQUENCIES[i], time);
gain.gain.exponentialRampToValueAtTime(VOLUMES[i], time);
}
}
oscillator.start(now);
oscillator.stop(now + TIMES[TIMES.length - 1]);
}
}
const SAVE_KEY = "birbSaveData"; const SAVE_KEY = "birbSaveData";
/** /**
@@ -1170,7 +1214,7 @@
class MenuItem { class MenuItem {
/** /**
* @param {string} text * @param {string|(() => string)} text
* @param {() => void} action * @param {() => void} action
* @param {boolean} [removeMenu] * @param {boolean} [removeMenu]
*/ */
@@ -1219,7 +1263,7 @@
if (item instanceof Separator) { if (item instanceof Separator) {
return makeElement("birb-window-separator"); return makeElement("birb-window-separator");
} }
let menuItem = makeElement("birb-menu-item", item.text); let menuItem = makeElement("birb-menu-item", typeof item.text === "function" ? item.text() : item.text);
onClick(menuItem, () => { onClick(menuItem, () => {
if (item.removeMenu) { if (item.removeMenu) {
removeMenuCallback(); removeMenuCallback();
@@ -1326,7 +1370,8 @@
* @typedef {typeof DEFAULT_SETTINGS} Settings * @typedef {typeof DEFAULT_SETTINGS} Settings
*/ */
const DEFAULT_SETTINGS = { const DEFAULT_SETTINGS = {
birbMode: false birbMode: false,
soundEnabled: true
}; };
// Rendering constants // Rendering constants
@@ -1539,6 +1584,7 @@
.birb-menu-item { .birb-menu-item {
width: calc(100% - var(--birb-double-border-size)); width: calc(100% - var(--birb-double-border-size));
white-space: nowrap;
font-size: 14px; font-size: 14px;
padding-top: 4px; padding-top: 4px;
padding-bottom: 4px; padding-bottom: 4px;
@@ -1811,12 +1857,16 @@
const settingsItems = [ const settingsItems = [
new MenuItem("Go Back", () => switchMenuItems(menuItems, updateMenuLocation), false), new MenuItem("Go Back", () => switchMenuItems(menuItems, updateMenuLocation), false),
new Separator(), new Separator(),
new MenuItem("Toggle Birb Mode", () => { new MenuItem(() => `${settings().soundEnabled ? "Disable" : "Enable"} Sound`, () => {
userSettings.birbMode = !userSettings.birbMode; userSettings.soundEnabled = !settings().soundEnabled;
save();
}),
new MenuItem(() => `Toggle ${birdBirb(true)} Mode`, () => {
userSettings.birbMode = !settings().birbMode;
save(); save();
const message = makeElement("birb-message-content"); const message = makeElement("birb-message-content");
message.appendChild(document.createTextNode(`Your ${birdBirb().toLowerCase()} shall now be referred to as "${birdBirb()}"`)); message.appendChild(document.createTextNode(`Your ${birdBirb().toLowerCase()} shall now be referred to as "${birdBirb()}"`));
if (userSettings.birbMode) { if (settings().birbMode) {
message.appendChild(document.createElement("br")); message.appendChild(document.createElement("br"));
message.appendChild(document.createElement("br")); message.appendChild(document.createElement("br"));
message.appendChild(document.createTextNode("Welcome back to 2012")); message.appendChild(document.createTextNode("Welcome back to 2012"));
@@ -1824,7 +1874,7 @@
insertModal(`${birdBirb()} Mode`, message); insertModal(`${birdBirb()} Mode`, message);
}), }),
new Separator(), new Separator(),
new MenuItem("2026.1.1", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.1.1"); }, false), new MenuItem("2026.1.4", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.1.4"); }, false),
]; ];
const styleElement = document.createElement("style"); const styleElement = document.createElement("style");
@@ -1838,6 +1888,8 @@
FLYING: "flying", FLYING: "flying",
}; };
const birdsong = new Birdsong();
let frozen = false; let frozen = false;
let stateStart = Date.now(); let stateStart = Date.now();
let currentState = States.IDLE; let currentState = States.IDLE;
@@ -1928,8 +1980,8 @@
/** /**
* Bird or birb, you decide * Bird or birb, you decide
*/ */
function birdBirb() { function birdBirb(invert = false) {
return settings().birbMode ? "Birb" : "Bird"; return settings().birbMode !== invert ? "Birb" : "Bird";
} }
function init() { function init() {
@@ -2518,6 +2570,9 @@
function pet() { function pet() {
if (currentState === States.IDLE && birb.getCurrentAnimation() !== Animations.HEART) { if (currentState === States.IDLE && birb.getCurrentAnimation() !== Animations.HEART) {
if (settings().soundEnabled) {
birdsong.chirp();
}
birb.setAnimation(Animations.HEART); birb.setAnimation(Animations.HEART);
lastPetTimestamp = Date.now(); lastPetTimestamp = Date.now();
} }

View File

@@ -857,6 +857,50 @@
} }
} }
// @ts-check
class Birdsong {
/**
* @type {AudioContext}
*/
audioContext;
chirp() {
if (!this.audioContext) {
this.audioContext = new AudioContext();
}
const TIMES = [0, 0.06, 0.10, 0.15];
const FREQUENCIES = [2200,
3500 + Math.random() * 600,
2100 + Math.random() * 200,
1600 + Math.random() * 400];
const VOLUMES = [0.0001, 0.3, 0.3, 0.0001];
const oscillator = this.audioContext.createOscillator();
oscillator.type = "sine";
const gain = this.audioContext.createGain();
oscillator.connect(gain);
gain.connect(this.audioContext.destination);
const now = this.audioContext.currentTime;
for (let i = 0; i < TIMES.length; i++) {
const time = TIMES[i] + now;
if (i === 0) {
oscillator.frequency.setValueAtTime(FREQUENCIES[i], time);
gain.gain.setValueAtTime(VOLUMES[i], time);
} else {
oscillator.frequency.exponentialRampToValueAtTime(FREQUENCIES[i], time);
gain.gain.exponentialRampToValueAtTime(VOLUMES[i], time);
}
}
oscillator.start(now);
oscillator.stop(now + TIMES[TIMES.length - 1]);
}
}
const SAVE_KEY = "birbSaveData"; const SAVE_KEY = "birbSaveData";
/** /**
@@ -1150,7 +1194,7 @@
class MenuItem { class MenuItem {
/** /**
* @param {string} text * @param {string|(() => string)} text
* @param {() => void} action * @param {() => void} action
* @param {boolean} [removeMenu] * @param {boolean} [removeMenu]
*/ */
@@ -1199,7 +1243,7 @@
if (item instanceof Separator) { if (item instanceof Separator) {
return makeElement("birb-window-separator"); return makeElement("birb-window-separator");
} }
let menuItem = makeElement("birb-menu-item", item.text); let menuItem = makeElement("birb-menu-item", typeof item.text === "function" ? item.text() : item.text);
onClick(menuItem, () => { onClick(menuItem, () => {
if (item.removeMenu) { if (item.removeMenu) {
removeMenuCallback(); removeMenuCallback();
@@ -1306,7 +1350,8 @@
* @typedef {typeof DEFAULT_SETTINGS} Settings * @typedef {typeof DEFAULT_SETTINGS} Settings
*/ */
const DEFAULT_SETTINGS = { const DEFAULT_SETTINGS = {
birbMode: false birbMode: false,
soundEnabled: true
}; };
// Rendering constants // Rendering constants
@@ -1519,6 +1564,7 @@
.birb-menu-item { .birb-menu-item {
width: calc(100% - var(--birb-double-border-size)); width: calc(100% - var(--birb-double-border-size));
white-space: nowrap;
font-size: 14px; font-size: 14px;
padding-top: 4px; padding-top: 4px;
padding-bottom: 4px; padding-bottom: 4px;
@@ -1791,12 +1837,16 @@
const settingsItems = [ const settingsItems = [
new MenuItem("Go Back", () => switchMenuItems(menuItems, updateMenuLocation), false), new MenuItem("Go Back", () => switchMenuItems(menuItems, updateMenuLocation), false),
new Separator(), new Separator(),
new MenuItem("Toggle Birb Mode", () => { new MenuItem(() => `${settings().soundEnabled ? "Disable" : "Enable"} Sound`, () => {
userSettings.birbMode = !userSettings.birbMode; userSettings.soundEnabled = !settings().soundEnabled;
save();
}),
new MenuItem(() => `Toggle ${birdBirb(true)} Mode`, () => {
userSettings.birbMode = !settings().birbMode;
save(); save();
const message = makeElement("birb-message-content"); const message = makeElement("birb-message-content");
message.appendChild(document.createTextNode(`Your ${birdBirb().toLowerCase()} shall now be referred to as "${birdBirb()}"`)); message.appendChild(document.createTextNode(`Your ${birdBirb().toLowerCase()} shall now be referred to as "${birdBirb()}"`));
if (userSettings.birbMode) { if (settings().birbMode) {
message.appendChild(document.createElement("br")); message.appendChild(document.createElement("br"));
message.appendChild(document.createElement("br")); message.appendChild(document.createElement("br"));
message.appendChild(document.createTextNode("Welcome back to 2012")); message.appendChild(document.createTextNode("Welcome back to 2012"));
@@ -1804,7 +1854,7 @@
insertModal(`${birdBirb()} Mode`, message); insertModal(`${birdBirb()} Mode`, message);
}), }),
new Separator(), new Separator(),
new MenuItem("2026.1.1", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.1.1"); }, false), new MenuItem("2026.1.4", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.1.4"); }, false),
]; ];
const styleElement = document.createElement("style"); const styleElement = document.createElement("style");
@@ -1818,6 +1868,8 @@
FLYING: "flying", FLYING: "flying",
}; };
const birdsong = new Birdsong();
let frozen = false; let frozen = false;
let stateStart = Date.now(); let stateStart = Date.now();
let currentState = States.IDLE; let currentState = States.IDLE;
@@ -1908,8 +1960,8 @@
/** /**
* Bird or birb, you decide * Bird or birb, you decide
*/ */
function birdBirb() { function birdBirb(invert = false) {
return settings().birbMode ? "Birb" : "Bird"; return settings().birbMode !== invert ? "Birb" : "Bird";
} }
function init() { function init() {
@@ -2498,6 +2550,9 @@
function pet() { function pet() {
if (currentState === States.IDLE && birb.getCurrentAnimation() !== Animations.HEART) { if (currentState === States.IDLE && birb.getCurrentAnimation() !== Animations.HEART) {
if (settings().soundEnabled) {
birdsong.chirp();
}
birb.setAnimation(Animations.HEART); birb.setAnimation(Animations.HEART);
lastPetTimestamp = Date.now(); lastPetTimestamp = Date.now();
} }

73
dist/web/birb.js vendored
View File

@@ -857,6 +857,50 @@
} }
} }
// @ts-check
class Birdsong {
/**
* @type {AudioContext}
*/
audioContext;
chirp() {
if (!this.audioContext) {
this.audioContext = new AudioContext();
}
const TIMES = [0, 0.06, 0.10, 0.15];
const FREQUENCIES = [2200,
3500 + Math.random() * 600,
2100 + Math.random() * 200,
1600 + Math.random() * 400];
const VOLUMES = [0.0001, 0.3, 0.3, 0.0001];
const oscillator = this.audioContext.createOscillator();
oscillator.type = "sine";
const gain = this.audioContext.createGain();
oscillator.connect(gain);
gain.connect(this.audioContext.destination);
const now = this.audioContext.currentTime;
for (let i = 0; i < TIMES.length; i++) {
const time = TIMES[i] + now;
if (i === 0) {
oscillator.frequency.setValueAtTime(FREQUENCIES[i], time);
gain.gain.setValueAtTime(VOLUMES[i], time);
} else {
oscillator.frequency.exponentialRampToValueAtTime(FREQUENCIES[i], time);
gain.gain.exponentialRampToValueAtTime(VOLUMES[i], time);
}
}
oscillator.start(now);
oscillator.stop(now + TIMES[TIMES.length - 1]);
}
}
const SAVE_KEY = "birbSaveData"; const SAVE_KEY = "birbSaveData";
/** /**
@@ -1150,7 +1194,7 @@
class MenuItem { class MenuItem {
/** /**
* @param {string} text * @param {string|(() => string)} text
* @param {() => void} action * @param {() => void} action
* @param {boolean} [removeMenu] * @param {boolean} [removeMenu]
*/ */
@@ -1199,7 +1243,7 @@
if (item instanceof Separator) { if (item instanceof Separator) {
return makeElement("birb-window-separator"); return makeElement("birb-window-separator");
} }
let menuItem = makeElement("birb-menu-item", item.text); let menuItem = makeElement("birb-menu-item", typeof item.text === "function" ? item.text() : item.text);
onClick(menuItem, () => { onClick(menuItem, () => {
if (item.removeMenu) { if (item.removeMenu) {
removeMenuCallback(); removeMenuCallback();
@@ -1306,7 +1350,8 @@
* @typedef {typeof DEFAULT_SETTINGS} Settings * @typedef {typeof DEFAULT_SETTINGS} Settings
*/ */
const DEFAULT_SETTINGS = { const DEFAULT_SETTINGS = {
birbMode: false birbMode: false,
soundEnabled: true
}; };
// Rendering constants // Rendering constants
@@ -1519,6 +1564,7 @@
.birb-menu-item { .birb-menu-item {
width: calc(100% - var(--birb-double-border-size)); width: calc(100% - var(--birb-double-border-size));
white-space: nowrap;
font-size: 14px; font-size: 14px;
padding-top: 4px; padding-top: 4px;
padding-bottom: 4px; padding-bottom: 4px;
@@ -1791,12 +1837,16 @@
const settingsItems = [ const settingsItems = [
new MenuItem("Go Back", () => switchMenuItems(menuItems, updateMenuLocation), false), new MenuItem("Go Back", () => switchMenuItems(menuItems, updateMenuLocation), false),
new Separator(), new Separator(),
new MenuItem("Toggle Birb Mode", () => { new MenuItem(() => `${settings().soundEnabled ? "Disable" : "Enable"} Sound`, () => {
userSettings.birbMode = !userSettings.birbMode; userSettings.soundEnabled = !settings().soundEnabled;
save();
}),
new MenuItem(() => `Toggle ${birdBirb(true)} Mode`, () => {
userSettings.birbMode = !settings().birbMode;
save(); save();
const message = makeElement("birb-message-content"); const message = makeElement("birb-message-content");
message.appendChild(document.createTextNode(`Your ${birdBirb().toLowerCase()} shall now be referred to as "${birdBirb()}"`)); message.appendChild(document.createTextNode(`Your ${birdBirb().toLowerCase()} shall now be referred to as "${birdBirb()}"`));
if (userSettings.birbMode) { if (settings().birbMode) {
message.appendChild(document.createElement("br")); message.appendChild(document.createElement("br"));
message.appendChild(document.createElement("br")); message.appendChild(document.createElement("br"));
message.appendChild(document.createTextNode("Welcome back to 2012")); message.appendChild(document.createTextNode("Welcome back to 2012"));
@@ -1804,7 +1854,7 @@
insertModal(`${birdBirb()} Mode`, message); insertModal(`${birdBirb()} Mode`, message);
}), }),
new Separator(), new Separator(),
new MenuItem("2026.1.1", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.1.1"); }, false), new MenuItem("2026.1.4", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.1.4"); }, false),
]; ];
const styleElement = document.createElement("style"); const styleElement = document.createElement("style");
@@ -1818,6 +1868,8 @@
FLYING: "flying", FLYING: "flying",
}; };
const birdsong = new Birdsong();
let frozen = false; let frozen = false;
let stateStart = Date.now(); let stateStart = Date.now();
let currentState = States.IDLE; let currentState = States.IDLE;
@@ -1908,8 +1960,8 @@
/** /**
* Bird or birb, you decide * Bird or birb, you decide
*/ */
function birdBirb() { function birdBirb(invert = false) {
return settings().birbMode ? "Birb" : "Bird"; return settings().birbMode !== invert ? "Birb" : "Bird";
} }
function init() { function init() {
@@ -2498,6 +2550,9 @@
function pet() { function pet() {
if (currentState === States.IDLE && birb.getCurrentAnimation() !== Animations.HEART) { if (currentState === States.IDLE && birb.getCurrentAnimation() !== Animations.HEART) {
if (settings().soundEnabled) {
birdsong.chirp();
}
birb.setAnimation(Animations.HEART); birb.setAnimation(Animations.HEART);
lastPetTimestamp = Date.now(); lastPetTimestamp = Date.now();
} }

View File

@@ -2,6 +2,7 @@ import Frame from './frame.js';
import Layer from './layer.js'; import Layer from './layer.js';
import Anim from './anim.js'; import Anim from './anim.js';
import { Birb, Animations } from './birb.js'; import { Birb, Animations } from './birb.js';
import { Birdsong } from './sound.js';
import { Context, ObsidianContext } from './context.js'; import { Context, ObsidianContext } from './context.js';
import { import {
@@ -60,7 +61,8 @@ import {
* @typedef {typeof DEFAULT_SETTINGS} Settings * @typedef {typeof DEFAULT_SETTINGS} Settings
*/ */
const DEFAULT_SETTINGS = { const DEFAULT_SETTINGS = {
birbMode: false birbMode: false,
soundEnabled: true
}; };
// Rendering constants // Rendering constants
@@ -176,12 +178,16 @@ function startApplication(birbPixels, featherPixels) {
const settingsItems = [ const settingsItems = [
new MenuItem("Go Back", () => switchMenuItems(menuItems, updateMenuLocation), false), new MenuItem("Go Back", () => switchMenuItems(menuItems, updateMenuLocation), false),
new Separator(), new Separator(),
new MenuItem("Toggle Birb Mode", () => { new MenuItem(() => `${settings().soundEnabled ? "Disable" : "Enable"} Sound`, () => {
userSettings.birbMode = !userSettings.birbMode; userSettings.soundEnabled = !settings().soundEnabled;
save();
}),
new MenuItem(() => `Toggle ${birdBirb(true)} Mode`, () => {
userSettings.birbMode = !settings().birbMode;
save(); save();
const message = makeElement("birb-message-content"); const message = makeElement("birb-message-content");
message.appendChild(document.createTextNode(`Your ${birdBirb().toLowerCase()} shall now be referred to as "${birdBirb()}"`)); message.appendChild(document.createTextNode(`Your ${birdBirb().toLowerCase()} shall now be referred to as "${birdBirb()}"`));
if (userSettings.birbMode) { if (settings().birbMode) {
message.appendChild(document.createElement("br")); message.appendChild(document.createElement("br"));
message.appendChild(document.createElement("br")); message.appendChild(document.createElement("br"));
message.appendChild(document.createTextNode("Welcome back to 2012")); message.appendChild(document.createTextNode("Welcome back to 2012"));
@@ -203,6 +209,8 @@ function startApplication(birbPixels, featherPixels) {
FLYING: "flying", FLYING: "flying",
}; };
const birdsong = new Birdsong();
let frozen = false; let frozen = false;
let stateStart = Date.now(); let stateStart = Date.now();
let currentState = States.IDLE; let currentState = States.IDLE;
@@ -293,8 +301,8 @@ function startApplication(birbPixels, featherPixels) {
/** /**
* Bird or birb, you decide * Bird or birb, you decide
*/ */
function birdBirb() { function birdBirb(invert = false) {
return settings().birbMode ? "Birb" : "Bird"; return settings().birbMode !== invert ? "Birb" : "Bird";
} }
function init() { function init() {
@@ -897,6 +905,9 @@ function startApplication(birbPixels, featherPixels) {
function pet() { function pet() {
if (currentState === States.IDLE && birb.getCurrentAnimation() !== Animations.HEART) { if (currentState === States.IDLE && birb.getCurrentAnimation() !== Animations.HEART) {
if (settings().soundEnabled) {
birdsong.chirp();
}
birb.setAnimation(Animations.HEART); birb.setAnimation(Animations.HEART);
lastPetTimestamp = Date.now(); lastPetTimestamp = Date.now();
} }

View File

@@ -12,7 +12,7 @@ export const MENU_EXIT_ID = "birb-menu-exit";
export class MenuItem { export class MenuItem {
/** /**
* @param {string} text * @param {string|(() => string)} text
* @param {() => void} action * @param {() => void} action
* @param {boolean} [removeMenu] * @param {boolean} [removeMenu]
*/ */
@@ -61,7 +61,7 @@ function makeMenuItem(item, removeMenuCallback) {
if (item instanceof Separator) { if (item instanceof Separator) {
return makeElement("birb-window-separator"); return makeElement("birb-window-separator");
} }
let menuItem = makeElement("birb-menu-item", item.text); let menuItem = makeElement("birb-menu-item", typeof item.text === "function" ? item.text() : item.text);
onClick(menuItem, () => { onClick(menuItem, () => {
if (item.removeMenu) { if (item.removeMenu) {
removeMenuCallback(); removeMenuCallback();

43
src/sound.js Normal file
View File

@@ -0,0 +1,43 @@
// @ts-check
export class Birdsong {
/**
* @type {AudioContext}
*/
audioContext;
chirp() {
if (!this.audioContext) {
this.audioContext = new AudioContext();
}
const TIMES = [0, 0.06, 0.10, 0.15];
const FREQUENCIES = [2200,
3500 + Math.random() * 600,
2100 + Math.random() * 200,
1600 + Math.random() * 400];
const VOLUMES = [0.0001, 0.3, 0.3, 0.0001];
const oscillator = this.audioContext.createOscillator();
oscillator.type = "sine";
const gain = this.audioContext.createGain();
oscillator.connect(gain);
gain.connect(this.audioContext.destination);
const now = this.audioContext.currentTime;
for (let i = 0; i < TIMES.length; i++) {
const time = TIMES[i] + now;
if (i === 0) {
oscillator.frequency.setValueAtTime(FREQUENCIES[i], time);
gain.gain.setValueAtTime(VOLUMES[i], time);
} else {
oscillator.frequency.exponentialRampToValueAtTime(FREQUENCIES[i], time);
gain.gain.exponentialRampToValueAtTime(VOLUMES[i], time);
}
}
oscillator.start(now);
oscillator.stop(now + TIMES[TIMES.length - 1]);
}
}

View File

@@ -198,6 +198,7 @@
.birb-menu-item { .birb-menu-item {
width: calc(100% - var(--birb-double-border-size)); width: calc(100% - var(--birb-double-border-size));
white-space: nowrap;
font-size: 14px; font-size: 14px;
padding-top: 4px; padding-top: 4px;
padding-bottom: 4px; padding-bottom: 4px;