diff --git a/chrome_extension/src/AlarmManager.js b/chrome_extension/src/AlarmManager.js index 1fb5d81..2616d54 100755 --- a/chrome_extension/src/AlarmManager.js +++ b/chrome_extension/src/AlarmManager.js @@ -40,8 +40,7 @@ class AlarmManager { this.bellTimer = bellTimer this.cookman = cookman this.started = false - // DEBUG - this.timeScale = bellTimer.devMode ? bellTimer.timeScale : 1 + this.timeScale = 1 } start () { diff --git a/css/style-mithril.css b/css/style-mithril.css index f2b123c..0a9bd9a 100755 --- a/css/style-mithril.css +++ b/css/style-mithril.css @@ -77,6 +77,7 @@ body { top: 50%; left: 50%; transform: translate(-50%, -50%); + z-index: 2; } .container { @@ -698,5 +699,5 @@ checkbox css is modified from https://kyusuf.com/post/completely-css-custom-chec left: 0; overflow: hidden; opacity: 1; - z-index: 2; + z-index: 0; } \ No newline at end of file diff --git a/src/BellTimer.js b/src/BellTimer.js index 1035512..96a9d0b 100644 --- a/src/BellTimer.js +++ b/src/BellTimer.js @@ -8,14 +8,13 @@ class BellTimer { this.cookieManager = cookieManager this.requestManager = requestManager - this.debug = function () {} - this.devMode = false this.startTime = 0 this.timeScale = 1 - var devModeCookie = this.cookieManager.get('dev_mode') + const devModeCookie = this.cookieManager.get('dev_mode') if (devModeCookie) { - this.enableDevMode(devModeCookie.startDate, devModeCookie.scale) + this.devMode = true + this.timeScale = devModeCookie.scale } } @@ -104,8 +103,7 @@ class BellTimer { interval: 4 * 60 * 1000 }) - ts.on('change', offset => - this.debug('Timesync offset: ' + offset)) + // ts.on('change', offset => {}) // Debug removed this.ts = ts diff --git a/src/ThemeManager.ts b/src/ThemeManager.ts index d8be339..e2bfc6b 100644 --- a/src/ThemeManager.ts +++ b/src/ThemeManager.ts @@ -9,11 +9,13 @@ import * as Jonathan from './themes/Jonathan' import { dark as PastelDark, light as PastelLight } from './themes/Pastel' import * as Quack from './themes/Quack' import { dark as RainbowDark, light as RainbowLight } from './themes/Rainbow' +import * as Valentines from './themes/Valentines' import * as Winter from './themes/Winter' const themes: ITheme[] = [ Winter, Halloween, + Valentines, Quack, DefaultLight, DefaultDark, diff --git a/src/themes/Valentines.js b/src/themes/Valentines.js new file mode 100644 index 0000000..2e7a569 --- /dev/null +++ b/src/themes/Valentines.js @@ -0,0 +1,211 @@ +const BasicColorTheme = require('./templates/BasicColorTheme') +const ColorSchemeTransformations = require('./ColorSchemeTransformations') +const BasicTiming = require('./timings/BasicTiming') + +const hearts = new Set() +const OFFSCREEN_BUFFER = 50 // Extra pixels below viewport before removing heart + +function drawHeart (ctx, x, y, size, rotation) { + ctx.save() + ctx.translate(x, y) + ctx.rotate(rotation) + ctx.beginPath() + const topCurveHeight = size * 0.3 + ctx.moveTo(0, topCurveHeight) + // Left curve + ctx.bezierCurveTo( + 0, 0, + -size / 2, 0, + -size / 2, topCurveHeight + ) + ctx.bezierCurveTo( + -size / 2, (topCurveHeight + size) / 2, + 0, (topCurveHeight + size) / 2, + 0, size + ) + // Right curve + ctx.bezierCurveTo( + 0, (topCurveHeight + size) / 2, + size / 2, (topCurveHeight + size) / 2, + size / 2, topCurveHeight + ) + ctx.bezierCurveTo( + size / 2, 0, + 0, 0, + 0, topCurveHeight + ) + ctx.closePath() + ctx.fill() + ctx.restore() +} + +// Helper function to draw a heart path (reusable for both background and foreground) +function drawHeartPath (ctx, w, h, yOffset) { + ctx.beginPath() + ctx.moveTo(0, h * 0.45 + yOffset) + ctx.bezierCurveTo( + -w * 0.4, h * 0.15 + yOffset, + -w, h * 0.05 + yOffset, + -w, -h * 0.15 + yOffset + ) + ctx.bezierCurveTo( + -w, -h * 0.42 + yOffset, + -w * 0.5, -h * 0.5 + yOffset, + 0, -h * 0.3 + yOffset + ) + ctx.bezierCurveTo( + w * 0.5, -h * 0.5 + yOffset, + w, -h * 0.42 + yOffset, + w, -h * 0.15 + yOffset + ) + ctx.bezierCurveTo( + w, h * 0.05 + yOffset, + w * 0.4, h * 0.15 + yOffset, + 0, h * 0.45 + yOffset + ) + ctx.closePath() +} + +function drawHeartCountdown (ctx, x, y, radius, proportion) { + const heartWidth = radius * 2 + const heartHeight = radius * 2 + const w = heartWidth * 0.5 + const h = heartHeight * 0.95 + const yOffset = h * 0.02 + // ===== DRAW BACKGROUND HEART (so they can see that its supposed to be a heart) ===== + ctx.save() + ctx.translate(x, y) + ctx.globalAlpha = 0.5 + drawHeartPath(ctx, w, h, yOffset) + ctx.fill() + ctx.restore() + // ===== DRAW FOREGROUND HEART (countdown, clipped) ===== + ctx.save() + ctx.beginPath() + ctx.arc(x, y, radius * 1.2, (Math.PI / -2), (Math.PI / -2) + (-2 * Math.PI) * (1 - proportion), true) + ctx.lineTo(x, y) + ctx.closePath() + ctx.clip() + ctx.globalAlpha = 0.98 + ctx.fillStyle = 'rgba(255,255,255,0.98)' + ctx.translate(x, y) + drawHeartPath(ctx, w, h, yOffset) + ctx.fill() + ctx.restore() +} + +module.exports = { + name: "Valentine's", + drawHeartCountdown: drawHeartCountdown, + enabled: (secrets) => { + const now = new Date() + // Only enabled for 10 days ending on Feb 14th + const month = now.getMonth() + 1 + const day = now.getDate() + return month === 2 && day >= 5 && day <= 14 + }, + specialEffects: (ctx, canvas) => { + if (hearts.size < 30 && Math.random() < 0.08) { + hearts.add({ + x: Math.random() * window.innerWidth, + y: -20, + size: 30 + Math.random() * 30, + rotation: Math.random() * Math.PI * 2, + rotationSpeed: (Math.random() - 0.5) * 0.05, + xSpeed: (Math.random() - 0.5) * 1.5, + ySpeed: 1 + Math.random() * 2, + color: Math.random() > 0.5 ? '#ff006e' : '#ff4d6d', + opacity: 0.6 + Math.random() * 0.4, + clicked: false, + pulsePhase: Math.random() * Math.PI * 2 + }) + } + + for (const heart of hearts) { + ctx.globalAlpha = heart.opacity + ctx.fillStyle = heart.clicked ? '#ffffff' : heart.color + + const pulseScale = 1 + Math.sin(heart.pulsePhase) * 0.3 + const currentSize = heart.clicked ? heart.size * 1.5 : heart.size + const renderedSize = currentSize * pulseScale + drawHeart(ctx, heart.x, heart.y, renderedSize, heart.rotation) + + if (heart.y > window.innerHeight + OFFSCREEN_BUFFER) { + hearts.delete(heart) + } + heart.y += heart.ySpeed + heart.x += heart.xSpeed + heart.rotation += heart.rotationSpeed + heart.pulsePhase += 0.05 + } + ctx.globalAlpha = 1.0 + canvas.addEventListener('mousedown', function (e) { + for (const heart of hearts) { + const pulseScale = 1 + Math.sin(heart.pulsePhase) * 0.3 + const currentSize = heart.clicked ? heart.size * 1.5 : heart.size + const renderedSize = currentSize * pulseScale + const distance = Math.sqrt(Math.pow(heart.x - e.x, 2) + Math.pow(heart.y - e.y, 2)) + if (distance < renderedSize) { + heart.clicked = !heart.clicked + break + } + } + }) + }, + theme: BasicColorTheme( + ColorSchemeTransformations.fromObjectStrings, + BasicTiming, + [{ + background: { + 'background-image': 'linear-gradient(135deg, #ff6b9d 0%, #ffc3e0 100%)', + 'background-color': '#ff85b3' + }, + text: '#4a0016', + subtext: '#7a1038', + contrast: 'white' + }, { + background: { + 'background-image': 'linear-gradient(135deg, #ff1f5a 0%, #ff6b9d 100%)', + 'background-color': '#ff4578' + }, + text: '#4a0016', + subtext: '#600020', + contrast: 'white' + }, { + background: { + 'background-image': 'linear-gradient(135deg, #c9184a 0%, #ff4d6d 100%)', + 'background-color': '#e6335b' + }, + text: '#ffffff', + subtext: '#ffc3e0', + contrast: 'white' + }, { + background: { + 'background-image': 'linear-gradient(135deg, #ff4d6d 0%, #ff758f 100%)', + 'background-color': '#ff617e' + }, + text: '#ffffff', + subtext: '#ffc3e0', + contrast: 'white' + }], { + holiday: { + background: { + 'background-image': 'linear-gradient(135deg, #ff006e 0%, #ff85b3 100%)', + 'background-color': '#ff4390' + }, + text: '#ffffff', + subtext: '#ffc3e0', + contrast: 'white' + }, + weekend: { + background: { + 'background-image': 'linear-gradient(135deg, #ff006e 0%, #ff85b3 100%)', + 'background-color': '#ff4390' + }, + text: '#ffffff', + subtext: '#ffc3e0', + contrast: 'white' + } + } + ) +} diff --git a/src/ui/Page1.js b/src/ui/Page1.js index 44eb147..9c39172 100644 --- a/src/ui/Page1.js +++ b/src/ui/Page1.js @@ -79,6 +79,7 @@ var updateGraphics = function (vnode) { var posX = width / 2 var posY = height / 2 + // Draw the background circle countdown ctx.beginPath() ctx.arc(posX, posY, radius, (Math.PI / -2), (Math.PI / -2) + (-2 * Math.PI) * (1 - proportion), true) ctx.lineTo(posX, posY) @@ -169,6 +170,12 @@ module.exports = { style: vnode.state.bottomStyle }, [ m(Background, vnode.attrs), + // Move snowflakes canvas to the back + m('canvas.snowflakes', { + onupdate: updateSnowflakes, + bellTimer: vnode.attrs.bellTimer, + themeManager: vnode.attrs.themeManager + }), m('.centered.time-text', [ m(SoundInteraction, vnode.attrs), m(Timer, vnode.attrs), @@ -179,11 +186,6 @@ module.exports = { onupdate: updateGraphics, bellTimer: vnode.attrs.bellTimer, themeManager: vnode.attrs.themeManager - }), - m('canvas.snowflakes', { - onupdate: updateSnowflakes, - bellTimer: vnode.attrs.bellTimer, - themeManager: vnode.attrs.themeManager }) ]) }