🚧 Basic settings

main
Basil 6 months ago
parent 000e52e121
commit 5362713eec

@ -34,6 +34,8 @@ The development URL will be copied to your clipboard.
The serving script `./serve.js` will listen on both ports 8000 and 8080 by default. Port 8000 serves the host part of the site (this corresponds to the production https://santatracker.google.com domain), and port 8080 serves the static content, including the scenes.
If you have Nix installed, you can run `nix-shell -p jre8`, followed by `node serve.js`.
To load a specific scene, open e.g., http://localhost:8000/boatload.html.
Once the site is loaded, you can also run `santaApp.route = 'sceneName'` in the console to switch scenes programmatically.

@ -3,6 +3,9 @@
"version": "2021.1.0",
"license": "Apache-2.0",
"scripts": {
"vite": "vite --port 3000",
"build": "vite build",
"dev": "npm run start",
"start": "./serve.js",
"release": "./release.js",
@ -61,6 +64,7 @@
},
"devDependencies": {
"@google-cloud/cloudbuild": "^2.6.0",
"@google-cloud/error-reporting": "^2.0.4"
"@google-cloud/error-reporting": "^2.0.4",
"vite": "^4.0.1"
}
}

@ -78,11 +78,5 @@
<a class="button" href="./?fallback=1&amp;ignore=1">Unsupported browsers</a>
</p>
</div>
<script>
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
ga('create', 'UA-37048309-' + (location.hostname === 'santatracker.google.com' ? 1 : 2), 'auto');
ga('send', 'pageview');
</script>
<script async src="https://www.google-analytics.com/analytics.js"></script>
</body>
</html>

@ -53,14 +53,6 @@
<link id="favicon" rel="shortcut icon" href="/images/favicon-64.png" sizes="64x64" />
<link id="favicon" rel="shortcut icon" href="/images/favicon-96.png" sizes="96x96" />
<!-- Analytics -->
<script>
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
ga('create', 'UA-37048309-' + (location.hostname === 'santatracker.google.com' ? 1 : 2), 'auto');
ga('set', 'transport', 'beacon');
</script>
<script async src="https://www.google-analytics.com/analytics.js"></script>
<!-- CSS -->
<style>
@keyframes loader-move {

@ -35,7 +35,6 @@ const documentLang = document.documentElement.lang || null;
const isProd = (documentLang !== null);
const fallback = checkFallback() || (location.search || '').match(/\bfallback=.*?\b/);
const ignoreErrors = (location.search || '').match(/\bignore=.*?\b/);
console.info('Santa Tracker', config.version, documentLang, fallback ? '(fallback)' : '');
// Global error handler. Redirect if we fail to load the entrypoint.
let loaded = false;

@ -71,11 +71,5 @@
<p msgid="upgrade-warning">Oops! Santa Tracker isn't supported in your browser or you have JavaScript disabled. To access Santa Tracker, download a modern browser or enable JavaScript.<br />For more information visit <a href="https://whatbrowser.org">whatbrowser.org</a></p>
<p><a class="button" href="./?ignore_browser_check=true" msgid="gotovillage">Go to the Village</a></p>
</div>
<script>
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
ga('create', 'UA-37048309-' + (location.hostname === 'santatracker.google.com' ? 1 : 2), 'auto');
ga('send', 'pageview');
</script>
<script async src="https://www.google-analytics.com/analytics.js"></script>
</body>
</html>

@ -26,7 +26,6 @@ document.adoptedStyleSheets = [styles];
import './src/elements/santa-chrome.js';
import './src/elements/santa-notice.js';
import './src/elements/santa-countdown.js';
import * as gameloader from './src/elements/santa-gameloader.js';
import './src/elements/santa-error.js';
import './src/elements/santa-badge.js';
@ -36,7 +35,6 @@ import './src/elements/santa-cardnav.js';
import './src/elements/santa-tutorial.js';
import './src/elements/santa-orientation.js';
import './src/elements/santa-interlude.js';
import {localStorage} from './src/storage.js';
import maybeLoadCast from './src/deps/cast.js';
import * as kplay from './src/kplay.js';
import {buildLoader} from './src/core/loader.js';
@ -61,23 +59,23 @@ const noticesElement = document.createElement('div');
noticesElement.className = 'notices';
document.body.append(noticesElement);
if (!isAndroid()) {
const cookieNoticeElement = document.createElement('santa-notice');
cookieNoticeElement.key = 'cookie-ok';
cookieNoticeElement.href = 'https://policies.google.com/technologies/cookies';
cookieNoticeElement.textContent = _msg`notice_cookies`;
noticesElement.append(cookieNoticeElement);
}
const upgradeNoticeElement = document.createElement('santa-notice');
upgradeNoticeElement.textContent = _msg`error-out-of-date`;
upgradeNoticeElement.hidden = !firebaseConfig.siteExpired();
firebaseConfig.listen(() => {
// nb. This has the unfortunate problem of bringing this message back very often, but that's
// probably fine, since the user is in a very bad state anyway.
upgradeNoticeElement.hidden = !firebaseConfig.siteExpired();
});
noticesElement.append(upgradeNoticeElement);
// if (!isAndroid()) {
// const cookieNoticeElement = document.createElement('santa-notice');
// cookieNoticeElement.key = 'cookie-ok';
// cookieNoticeElement.href = 'https://policies.google.com/technologies/cookies';
// cookieNoticeElement.textContent = _msg`notice_cookies`;
// noticesElement.append(cookieNoticeElement);
// }
// const upgradeNoticeElement = document.createElement('santa-notice');
// upgradeNoticeElement.textContent = _msg`error-out-of-date`;
// upgradeNoticeElement.hidden = !firebaseConfig.siteExpired();
// firebaseConfig.listen(() => {
// // nb. This has the unfortunate problem of bringing this message back very often, but that's
// // probably fine, since the user is in a very bad state anyway.
// upgradeNoticeElement.hidden = !firebaseConfig.siteExpired();
// });
// noticesElement.append(upgradeNoticeElement);
const loaderElement = document.createElement('santa-gameloader');
const interludeElement = document.createElement('santa-interlude');
@ -133,81 +131,6 @@ chromeElement.addEventListener('sidebar-open', (ev) => {
refreshInsets();
}());
// Controls the random future games that a user is suggested.
(function() {
const recentBuffer = 6;
const displayCardCount = 2;
const recentRoutes = new Set();
window.addEventListener('entrypoint-route', (ev) => {
const route = ev.detail;
global.setState({route});
recentRoutes.add(route);
while (recentRoutes.size >= recentBuffer) {
for (const key of recentRoutes) {
recentRoutes.delete(key);
break;
}
}
updatePlayNextCards(route);
});
function updatePlayNextCards(currentRoute) {
// array of all possible games
const nav = firebaseConfig.nav().filter((x) => x[0] !== '@' && !firebaseConfig.isLocked(x));
if (nav.length <= displayCardCount) {
console.warn('not enough valid nav routes to create cards', nav)
return;
}
// choose games biasing towards start
const cards = [];
const attempts = 20;
let i = 0;
while (cards.length < displayCardCount) {
++i;
const index = ~~(Math.pow(Math.random(), 4) * nav.length);
const choice = nav.splice(index, 1)[0];
if ((recentRoutes.has(choice) || cards.indexOf(choice) !== -1) && i < attempts) {
continue;
}
cards.push(choice);
}
scoreOverlayElement.textContent = '';
cards.forEach((scene) => {
const card = document.createElement('santa-card');
card.scene = scene;
scoreOverlayElement.append(card);
});
let playNextRoute = cards[0];
let featured = firebaseConfig.featuredRoute();
if (featured === 'takeoff' || featured === 'liftoff') {
// FIXME: hack for HPP
featured = 'likealight';
}
if (!featured) {
// Do nothing, no featured.
} else if (currentRoute === featured) {
// We just chose this route. Mark it as "done".
localStorage['featured_played'] = featured;
} else if (localStorage['featured_played'] !== featured) {
// This route hasn't yet been played, so make it the Play Next route.
playNextRoute = featured;
}
console.warn('playNext is', playNextRoute);
global.setState({playNextRoute});
}
}());
const loadMethod = loaderElement.load.bind(loaderElement);
const {scope, go, write: writeData} = configureProdRouter(buildLoader(loadMethod));
document.body.addEventListener('click', globalClickHandler(scope, go));
@ -288,7 +211,6 @@ global.subscribe((state) => {
// This happens first, as we modify state as a side-effect.
if (state.status === 'restart') {
state.status = ''; // nb. modifies state as side effect
ga('send', 'event', 'game', 'start', state.route);
state.control.send({type: 'restart'});
}
@ -538,18 +460,6 @@ async function runner(control, route) {
const sc = kplayInstance;
let recentScore = null;
// nb. we also call this as a result of 'restart'
ga('send', 'event', 'game', 'start', route);
const analyticsLogEnd = () => {
if (!recentScore) {
return;
}
ga('send', 'event', 'game', 'end', route);
recentScore.score && ga('send', 'event', 'game', 'score', route, recentScore.score);
recentScore.level && ga('send', 'event', 'game', 'level', route, recentScore.level);
recentScore = null;
};
for (;;) {
const op = await control.next();
if (op === null || op === undefined) {
@ -565,10 +475,6 @@ async function runner(control, route) {
sc.play(...payload);
continue;
case 'ga':
ga.apply(null, payload);
continue;
case 'go':
go(payload);
continue;
@ -582,7 +488,6 @@ async function runner(control, route) {
global.setState({
status: 'gameover',
});
analyticsLogEnd();
continue;
case 'score':
@ -605,8 +510,6 @@ async function runner(control, route) {
console.debug('running scene unhandled', op);
}
analyticsLogEnd();
}

@ -45,6 +45,8 @@
<script src="../../third_party/lib/phaser/phaser-arcade-physics.js"></script>
<script src="../../node_modules/jquery/dist/jquery.min.js"></script>
<script type="module">
// import * as peng from '../../src/mod/settings.js';
// console.log(peng);
import api from '../../src/scene/api.js';
import Game from './:closure.js';

@ -189,12 +189,12 @@ app.Game.prototype.gameover = function() {
this.freezeGame();
this.gameoverView.show();
window.santaApp.fire('sound-trigger', 'music_ingame_gameover');
window.santaApp.fire('analytics-track-game-over', {
gameid: 'penguindash',
score: this.scoreboard.score,
level: this.level,
timePlayed: new Date - this.gameStartTime
});
// window.santaApp.fire('analytics-track-game-over', {
// gameid: 'penguindash',
// score: this.scoreboard.score,
// level: this.level,
// timePlayed: new Date - this.gameStartTime
// });
};
@ -295,13 +295,6 @@ app.Game.prototype.watchOrientation_ = function() {
* @export
*/
app.Game.prototype.dispose = function() {
if (this.isPlaying) {
window.santaApp.fire('analytics-track-game-quit', {
gameid: 'penguindash',
timePlayed: new Date - this.gameStartTime,
level: this.level
});
}
this.freezeGame();
$(window).off('.penguindash');

@ -609,7 +609,6 @@ app.Start.prototype.overlapPrize_ = function(penguin, prize) {
this.game.st_parent.scoreboard.addScore(app.Constants.POINTS_GIFT_BASIC);
};
/**
* Run animation for falling and restart level.
* @private
@ -627,7 +626,10 @@ app.Start.prototype.dieAndRestart_ = function() {
window.setTimeout(() => {
this.dead = false;
this.restartLevel_();
this.game.st_parent.scoreboard.onFrame(-app.Constants.TIME_LOSE);
// console.log(window.peng);
if (!window.peng.getSetting('disableDeathPenalty')) this.game.st_parent.scoreboard.onFrame(-app.Constants.TIME_LOSE);
// this.game.st_parent.scoreboard.onFrame(-app.Constants.TIME_LOSE);
// this.game.st_parent.scoreboard.onFrame(-config('timeLose'));
}, 2500);
}
@ -680,12 +682,12 @@ app.Start.prototype.gameover_ = function() {
this.game.st_parent.gameoverView.show();
window.santaApp.fire('sound-trigger', 'music_ingame_gameover');
window.santaApp.fire('sound-trigger', 'pnd_game_over');
window.santaApp.fire('analytics-track-game-over', {
gameid: 'penguindash',
score: this.scoreboard.score,
level: this.level,
timePlayed: new Date - this.gameStartTime
});
// window.santaApp.fire('analytics-track-game-over', {
// gameid: 'penguindash',
// score: this.scoreboard.score,
// level: this.level,
// timePlayed: new Date - this.gameStartTime
// });
};

@ -22,7 +22,6 @@
*/
import {frame} from '../lib/promises.js';
import {localStorage} from '../storage.js';
const firebase = window.firebase;
const remoteConfig = firebase.remoteConfig();
@ -47,34 +46,6 @@ export function siteExpired() {
return isOutOfDate;
}
function checkUpgrade() {
const siteVersion = window.santaConfig.version;
const upgradeToVersion = remoteConfig.getString('upgradeToVersion');
if (!upgradeToVersion || upgradeToVersion < siteVersion) {
isOutOfDate = false;
delete localStorage['upgradeToVersion'];
return; // nothing to do
}
// otherwise, reload or complain
console.warn('got out-of-date version, have', siteVersion, 'want', upgradeToVersion);
if (!isProd || localStorage['upgradeToVersion'] === upgradeToVersion) {
ga('send', 'event', 'site', 'upgrade-warn', upgradeToVersion);
isOutOfDate = true;
notifyListeners();
} else {
ga('send', 'event', 'site', 'upgrade-attempt', upgradeToVersion);
localStorage['upgradeToVersion'] = upgradeToVersion;
window.location.href = window.location.href;
}
}
checkUpgrade();
window._check = checkUpgrade;
function refreshMemoized() {
for (const key in memoized) {
let cand;
@ -205,7 +176,6 @@ export async function refresh(invalidateNow = false) {
await remoteConfig.fetchAndActivate();
refreshMemoized();
checkUpgrade();
// nb. It's not clear RC tells us if it changes or not. Just notify everyone anyway.
notifyListeners();

@ -67,9 +67,6 @@ export function buildLoader(loadMethod, fallback=false) {
window.dispatchEvent(new CustomEvent('loader-route', {detail: route}));
ga('set', 'page', `/${route}`);
ga('send', 'pageview');
const locked = (activeSceneName === null);
loadMethod(url, {route, data, locked}).then((success) => {
document.title = scenes[route] || _msg`santatracker`;

@ -88,7 +88,7 @@ export class SantaChromeElement extends LitElement {
const sidebarId = `${this._id}sidebar`; // unique ID even in Shady DOM
// Either "Santa's Village" for the home button, or "View All" for all games.
const labelForMenu = this.showHome ? labels.home : labels.menu;
const labelForRestart = labels.restart;
const labelForAudio = this.muted ? labels.mute : labels.unmute;
const labelForAction = labels[this.action] || ''
@ -100,13 +100,12 @@ export class SantaChromeElement extends LitElement {
</div>
<div id="padder">
<header>
<santa-button aria-label=${labelForMenu} @click=${this._onMenuClick} path=${this.showHome ? paths.home : paths.menu}></santa-button>
<santa-button aria-label=${labelForRestart} @click=${this._onRestartClick} path=${paths.restart}></santa-button>
<santa-button .hidden=${isAndroid()} aria-label=${labelForAudio} color=${this.muted ? 'purple' : ''} @click=${this._onAudioClick} path=${this.muted ? paths.unmute : paths.mute}></santa-button>
<santa-button aria-label=${labelForAction} ?disabled=${!this.action} @click=${this._onActionClick} path=${paths[this.action || this._lastAction] || ''}></santa-button>
<div class="grow"></div>
<div><slot name="game"></slot></div>
<div class="grow"></div>
<santa-countdown .until=${countdownTo}></santa-countdown>
<div id="active-fixer"></div>
</header>
</div>
@ -155,18 +154,11 @@ export class SantaChromeElement extends LitElement {
}
_onAudioClick() {
window.ga('send', 'event', 'nav', 'click', this.muted ? 'unmute' : 'mute');
this.dispatchEvent(new CustomEvent('audio', {detail: this.muted}));
}
_onMenuClick() {
if (this.showHome) {
window.ga('send', 'event', 'nav', 'click', 'home');
window.dispatchEvent(new CustomEvent(common.goEvent)); // home
} else {
window.ga('send', 'event', 'nav', 'click', 'menu');
this.navOpen = !this.navOpen;
}
_onRestartClick() {
this.dispatchEvent(new CustomEvent('restart'));
}
_onSidebarClick(e) {
@ -181,7 +173,6 @@ export class SantaChromeElement extends LitElement {
_onActionClick(e) {
window.ga('send', 'event', 'nav', 'click', this.action);
this.dispatchEvent(new CustomEvent('action', {
detail: this.action,
bubbles: true,

@ -1,162 +0,0 @@
/**
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {html, LitElement} from 'lit-element';
import {countdownSplit} from '../lib/time.js';
import styles from './santa-countdown.css';
import {_msg} from '../magic.js';
import {ifDefined} from 'lit-html/directives/if-defined';
function pad(x) {
if (x == null) {
return '';
}
x = ~~x || 0;
return x < 10 ? `0${x}` : x;
}
export class SantaCountdownElement extends LitElement {
static get properties() {
return {
until: {type: Number}, // The specific Date to count to.
_currentSplit: {type: Object},
_previousSplit: {type: Object},
_hideOffset: {type: Number},
};
}
constructor() {
super();
this._currentSplit = {count: 0};
this._previousSplit = {count: 0};
this.until = 0;
this._interval = 0;
this._hideOffset = 0;
}
static get styles() {
return [styles];
}
_tick() {
this._previousSplit = this._currentSplit;
const now = +new Date;
const s = countdownSplit(this.until - now);
this._currentSplit = s;
if (!window.Intl || !window.Intl.RelativeTimeFormat) {
return;
}
const key = ['', 'seconds', 'minutes', 'hours', 'days'][s.count] || '';
if (!key) {
this.removeAttribute('aria-label');
} else {
const formatter = new Intl.RelativeTimeFormat();
const label = formatter.format(s[key], key);
this.setAttribute('aria-label', label + '\n' + _msg`countdownlabel`);
}
}
update(changedProperties) {
if (changedProperties.has('until')) {
const now = +new Date;
if (this.until < now) {
window.clearInterval(this._interval);
this._interval = 0;
this._currentSplit = {count: 0};
} else if (!this._interval) {
this._tick();
this._interval = window.setInterval(() => this._tick(), 1000);
}
}
super.update(changedProperties);
}
_animationEnd(event) {
const node = event.target.closest('.counter-box');
this._previousSplit = {...this._previousSplit, [node.getAttribute('data-key')]: undefined};
}
render() {
const prev = this._previousSplit;
const split = this._currentSplit;
const isHuge = (split.days >= 300);
const classFor = (x) => {
return (split[x] !== prev[x] && prev[x] !== undefined) ? 'anim' : '';
};
// Generates the order in which these elements should be hidden.
const hide = (value) => {
if (!(value < 4 && isHuge)) {
const out = value - split.count + 4;
if (out <= 4) {
return out;
}
}
return ifDefined(undefined);
};
return html`
<main @animationend=${this._animationEnd} class=${split.count ? '' : 'done'}>
<svg viewBox="0 0 11 44" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMaxYMid meet">
<path d="M11,0 C9.18902037,0.133131303 7.51223617,0.599091955 6.23781871,1.99697318 C3.68903501,4.19364368 4.09147493,7.85478539 4.42683908,10.7836552 C4.56098328,12.2481264 4.6951348,13.779115 4.62805904,15.4432835 C4.56098328,19.3706399 2.81707942,21.5007204 0,22.033295 C2.81707942,22.565797 4.56098328,24.6958775 4.62805904,28.6233065 C4.6951348,30.2874024 4.56098328,31.8184636 4.42683908,33.2163448 C4.09147493,36.1452146 3.75610345,39.8063563 6.23781871,42.0030268 C7.51223617,43.400908 9.18902037,43.93341 11,44 L11,0 Z"/>
</svg>
<div class="inner">
<div class="counter-box ${classFor('days')} ${split.days >= 100 ? 'large' : ''}" data-key="days" data-order=${hide(4)}>
<div class="holder active">${pad(split.days)}</div>
<div class="holder prev">${pad(prev.days)}</div>
<h2>${_msg`countdown_days`}</h2>
</div>
<div class="counter-box ${classFor('hours')}" data-key="hours" data-order=${hide(3)}>
<div class="holder active">${pad(split.hours)}</div>
<div class="holder prev">${pad(prev.hours)}</div>
<h2>${_msg`countdown_hours`}</h2>
</div>
<div class="counter-box ${classFor('minutes')}" data-key="minutes" data-order=${hide(2)}>
<div class="holder active">${pad(split.minutes)}</div>
<div class="holder prev">${pad(prev.minutes)}</div>
<h2>${_msg`countdown_minutes`}</h2>
</div>
<div class="counter-box ${classFor('seconds')}" data-key="seconds" data-order=${hide(1)}>
<div class="holder active">${pad(split.seconds)}</div>
<div class="holder prev">${pad(prev.seconds)}</div>
<h2>${_msg`countdown_seconds`}</h2>
</div>
</div>
<svg viewBox="0 0 11 44" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMid meet">
<g transform="translate(5.5, 22) scale(-1, 1) translate(-5.5, -22)">
<path d="M11,0 C9.18902037,0.133131303 7.51223617,0.599091955 6.23781871,1.99697318 C3.68903501,4.19364368 4.09147493,7.85478539 4.42683908,10.7836552 C4.56098328,12.2481264 4.6951348,13.779115 4.62805904,15.4432835 C4.56098328,19.3706399 2.81707942,21.5007204 0,22.033295 C2.81707942,22.565797 4.56098328,24.6958775 4.62805904,28.6233065 C4.6951348,30.2874024 4.56098328,31.8184636 4.42683908,33.2163448 C4.09147493,36.1452146 3.75610345,39.8063563 6.23781871,42.0030268 C7.51223617,43.400908 9.18902037,43.93341 11,44 L11,0 Z"/>
</g>
</svg>
</div>
`;
}
}
customElements.define('santa-countdown', SantaCountdownElement);

@ -1,225 +0,0 @@
/**
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
$tick-time: 0.6s; // time for number to tick over
$bar-color: #ff0160;
$mobile-width: 768px;
$height: 60px;
$mobile-height: 52px;
$flourish-indent: 6px;
:host {
display: block;
will-change: transform;
transform: translateZ(0);
}
:host([hidden]) {
display: none;
}
main {
font-family: 'Roboto', 'Arial', Sans-Serif;
text-align: center;
user-select: none;
display: flex;
justify-content: stretch;
// nb: we expand #counter up/down so that its layer can include the animation, otherwise the
// outer element (or at worst, the page) needs rerendering.
$overflow: 100px / 2; // TODO(samthor): We assume box is max 100px.
margin: -($overflow) 0;
padding: $overflow 0;
z-index: 100;
position: relative;
height: $mobile-height;
@media (min-width: $mobile-width) {
height: $height;
}
filter: drop-shadow(3px 8px 0 rgba(0, 0, 0, 0.1));
&.done {
display: none;
}
}
svg {
fill: var(--color-bar, #ff3333);
height: 100%;
margin: 0 (-$flourish-indent - 1); // extra pixel for safari
position: relative;
height: $mobile-height;
@media (min-width: $mobile-width) {
height: $height;
}
}
.inner {
display: flex;
justify-content: stretch;
align-items: stretch;
position: relative;
&::before {
z-index: -1;
background: var(--color-bar, #ff3333);;
content: '';
position: absolute;
top: 0;
left: $flourish-indent;
right: $flourish-indent;
bottom: 0;
}
}
// draws the line between counter-box elements which are not hidden
.counter-box[data-order] + .counter-box {
margin-left: 1px;
&:before {
content: '';
position: absolute;
left: -1px;
top: 7px;
bottom: 7px;
width: 1px;
background: rgba(0, 0, 0, 0.25);
display: block;
}
}
.counter-box {
width: 40px;
position: relative;
&.large {
width: 48px;
}
&.huge {
min-width: 48px;
width: auto !important;
}
.holder {
width: 100%;
height: 100%;
font-size: 22px;
font-family: 'Lobster';
color: white;
font-weight: 500;
line-height: $mobile-height - 8px;
margin-bottom: 8px;
text-shadow: none;
position: absolute;
transform: translateY(0);
will-change: transform;
z-index: 100;
animation-duration: $tick-time;
// `text-align: center` seems not to work on -ve letter-spacing
letter-spacing: -1px;
&.prev {
transform: translateY(100%);
opacity: 0;
}
}
&.anim {
.holder.active {
animation-name: counter-step-active;
}
.holder.prev {
animation-name: counter-step-prev;
}
}
@media (min-width: $mobile-width) {
width: 44px;
.holder {
font-size: 26px;
line-height: $height - 8px;
}
&.large {
width: 52px;
}
}
}
h2 {
font-size: 8px;
line-height: 8px;
font-weight: 600;
color: #fff;
margin: 0;
text-transform: uppercase;
letter-spacing: 1px;
position: absolute;
bottom: 8px;
left: 0;
right: 0;
opacity: 0.8;
}
.counter-box:not([data-order]) {
display: none;
}
@media (max-width: 386px) {
.counter-box[data-order="3"] {
display: none;
}
}
@media (max-width: 422px) {
.counter-box[data-order="2"] {
display: none;
}
}
@media (max-width: 458px) {
.counter-box[data-order="1"] {
display: none;
}
}
@keyframes counter-step-active {
0% {
transform: translateY(50%);
opacity: 0;
}
100% {
transform: translateY(0%);
opacity: 1;
}
}
@keyframes counter-step-prev {
0% {
transform: translateY(0%);
opacity: 1;
}
100% {
transform: translateY(-50%);
opacity: 0;
}
}

@ -315,7 +315,6 @@ class SantaGameLoaderElement extends HTMLElement {
const port = await prepareMessage(frame, 30 * 1000);
if (frame !== this._activeFrame) {
window.ga('send', 'event', 'nav', 'preempted', 'load');
return null; // check for preempt
}
@ -331,7 +330,6 @@ class SantaGameLoaderElement extends HTMLElement {
ready: () => {
resolve(port ? control : null); // resolve with null if there's no scene here
if (frame !== this._activeFrame) {
window.ga('send', 'event', 'nav', 'preempted', 'port');
return false; // no longer active
}
this.purge();

@ -33,7 +33,6 @@ window.addEventListener('beforeinstallprompt', (event) => {
event.preventDefault();
installEvent = event;
notifyInstances();
window.ga('send', 'event', 'app', 'beforeinstallprompt');
console.info('beforeinstallprompt');
});
@ -41,7 +40,6 @@ window.addEventListener('beforeinstallprompt', (event) => {
window.addEventListener('appinstalled', () => {
installEvent = null;
notifyInstances();
window.ga('send', 'event', 'app', 'appinstalled');
console.info('appinstalled');
});
@ -76,15 +74,11 @@ class SantaInstallElement extends LitElement {
event.stopPropagation();
if (!installEvent) {
if (isIos) {
window.ga('send', 'event', 'nav', 'click', 'install-ios');
}
event.target.focus(); // Safari doesn't focus <button> by default
return; // this can happen on iOS, when clicking does nothing
}
installEvent.prompt();
window.ga('send', 'event', 'nav', 'click', 'install');
const savedEvent = installEvent;
Promise.resolve(installEvent.userChoice || 'unknown').then((choiceResult) => {
@ -93,7 +87,6 @@ class SantaInstallElement extends LitElement {
installEvent = null;
notifyInstances();
}
window.ga('send', 'event', 'app', 'install', choiceResult);
});
}

@ -17,38 +17,9 @@
import {html, LitElement} from 'lit-element';
import {until} from 'lit-html/directives/until.js';
import styles from './santa-overlay.css';
import * as common from '../core/common.js';
import {_msg} from '../magic.js';
import './santa-button.js';
async function shortenUrl(raw) {
// Firebase is only configured to serve links under `https://santatracker.google.com`.
const key = 'AIzaSyBrNcGcna0TMn2uLRxhMBwxVwXUBjlZqzU';
const domain = 'https://santatracker.google.com';
if (!raw) {
return '';
}
const ensureDomainURL = new URL(raw, domain);
const url = new URL(ensureDomainURL.pathname + ensureDomainURL.search, domain);
const response = await window.fetch(`https://firebasedynamiclinks.googleapis.com/v1/shortLinks?key=${key}`, {
method: 'POST',
body: JSON.stringify({dynamicLinkInfo: {
domainUriPrefix: 'https://santatracker.page.link',
link: url.toString(),
}}),
});
const body = await response.json();
if ('shortLink' in body) {
return body['shortLink'];
}
throw new Error(`shortLink invalid data: ${JSON.stringify(body)}`);
}
import { getSetting, setSetting, settings } from '../mod/settings.js';
export class SantaOverlayElement extends LitElement {
static get properties() {
@ -72,14 +43,15 @@ export class SantaOverlayElement extends LitElement {
this.dispatchEvent(new CustomEvent('resume'));
}
_dispatchHome() {
window.dispatchEvent(new CustomEvent(common.goEvent)); // home
_toggleSetting(e) {
setSetting(e.target.id, e.target.checked);
}
update(changedProperties) {
if (changedProperties.has('shareUrl')) {
this._shortUrl = shortenUrl(this.shareUrl);
this._shortUrl = this.shareUrl;
}
return super.update(changedProperties);
}
@ -89,7 +61,6 @@ export class SantaOverlayElement extends LitElement {
input.select();
document.execCommand('copy');
input.setSelectionRange(0, 0);
window.ga('send', 'event', 'nav', 'click', 'copy-url');
input.classList.add('copy');
window.requestAnimationFrame(() => {
@ -119,15 +90,16 @@ export class SantaOverlayElement extends LitElement {
<santa-button aria-label=${_msg`playagain`} color="purple" @click="${this._dispatchRestart}" id="playagainButton">
<svg class="icon"><path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg>
</santa-button>
<santa-button aria-label=${_msg`santasvillage`} color="theme" @click="${this._dispatchHome}" data-action="home">
<svg class="icon"><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>
</santa-button>
</div>
</nav>
<div class="settings">
<h3>Settings</h3>
${settings.map(setting => html`<label>
${setting.type === 'boolean' && html`<input type="checkbox" @change="${this._toggleSetting}" id="${setting.key}" .checked=${getSetting(setting.key)}></input>`}
<a>${setting.name}</a>
</label>`)}
</div>
</main>
<div class="below" @click=${this._onBelowClick}>
<slot></slot>
</div>
</div>
`;
}
@ -139,12 +111,6 @@ export class SantaOverlayElement extends LitElement {
this.shadowRoot.querySelector('#playagainButton').focus();
}
}
_onBelowClick(event) {
if (event.target && event.target.localName === 'santa-card') {
window.ga('send', 'event', 'nav', 'click', 'below-card');
}
}
}

@ -18,211 +18,284 @@ $duration: 0.4s;
$opacity-duration-out: 0.6s;
@keyframes santa-overlay__hide {
from { visibility: visible; pointer-events: none; }
to { visibility: hidden; pointer-events: none; }
from {
visibility: visible;
pointer-events: none;
}
to {
visibility: hidden;
pointer-events: none;
}
}
:host {
margin: 0;
padding: 0;
margin: 0;
padding: 0;
}
svg.icon {
margin: auto;
width: 24px;
height: 24px;
fill: currentColor;
margin: auto;
width: 24px;
height: 24px;
fill: currentColor;
}
.backdrop {
display: flex;
align-items: center;
justify-content: center;
flex-flow: column;
z-index: 11;
transition: opacity $duration ease-in-out;
display: flex;
align-items: center;
justify-content: center;
flex-flow: column;
z-index: 11;
transition: opacity $duration ease-in-out;
}
main {
display: flex;
flex-flow: column;
align-items: center;
justify-content: center;
display: flex;
flex-flow: column;
align-items: center;
justify-content: center;
position: relative;
position: relative;
transform: translate(0);
transition: transform $duration cubic-bezier(0,1.52,.295,.86);
transform: translate(0);
transition: transform $duration cubic-bezier(0, 1.52, 0.295, 0.86);
font-family: 'Google Sans', 'Arial', Sans-Serif;
font-family: 'Google Sans', 'Arial', Sans-Serif;
}
:host([hidden]) {
display: block;
visibility: hidden;
animation: santa-overlay__hide $opacity-duration-out step-end forwards;
display: block;
visibility: hidden;
animation: santa-overlay__hide $opacity-duration-out step-end forwards;
}
:host([hidden]) .backdrop {
opacity: 0;
transition-duration: $opacity-duration-out;
opacity: 0;
transition-duration: $opacity-duration-out;
}
:host([hidden]) main {
transform: translateY(-100vh);
transition-timing-function: cubic-bezier(.83,.02,1,.01);
transform: translateY(-100vh);
transition-timing-function: cubic-bezier(0.83, 0.02, 1, 0.01);
}
nav,
.hero {
min-width: 240px;
min-width: 240px;
}
nav {
pointer-events: auto;
background: rgba(255,255,255,.25);
border-radius: 12px;
text-align: center;
display: flex;
flex-direction: column;
box-sizing: border-box;
padding: 12px;
position: relative;
h4 {
color: #fff;
font-size: .6em;
line-height: 2em;
font-weight: 800;
margin: 0;
margin-top: -8px;
text-transform: uppercase;
}
.url {
position: relative;
input {
cursor: copy;
width: 100%;
border: 0;
border-radius: 4px;
font: inherit;
font-size: 12px;
text-align: center;
font-weight: 400;
letter-spacing: -0.5px;
padding: 4px;
box-shadow: inset 0 0 4px rgba(0, 0, 0, 0.125);
box-sizing: border-box;
margin-bottom: 8px;
background: white;
color: black;
transition: background 0.25s, color 0.25s;
&.copy {
transition: none;
background: black;
color: white;
}
}
}
.buttons {
display: flex;
align-items: center;
justify-content: center;
margin: 0 4px;
santa-button {
padding: 2px;
margin: 0 4px;
width: 44px;
height: 44px;
svg {
position: absolute;
margin: auto;
}
}
}
pointer-events: auto;
background: rgba(255, 255, 255, 0.25);
border-radius: 12px;
text-align: center;
display: flex;
flex-direction: column;
box-sizing: border-box;
padding: 12px;
position: relative;
h4 {
color: #fff;
font-size: 0.6em;
line-height: 2em;
font-weight: 800;
margin: 0;
margin-top: -8px;
text-transform: uppercase;
}
.url {
position: relative;
input {
cursor: copy;
width: 100%;
border: 0;
border-radius: 4px;
font: inherit;
font-size: 12px;
text-align: center;
font-weight: 400;
letter-spacing: -0.5px;
padding: 4px;
box-shadow: inset 0 0 4px rgba(0, 0, 0, 0.125);
box-sizing: border-box;
margin-bottom: 8px;
background: white;
color: black;
transition: background 0.25s, color 0.25s;
&.copy {
transition: none;
background: black;
color: white;
}
}
}
.buttons {
display: flex;
align-items: center;
justify-content: center;
margin: 0 4px;
santa-button {
padding: 2px;
margin: 0 4px;
width: 44px;
height: 44px;
svg {
position: absolute;
margin: auto;
}
}
}
}
.hero {
flex-grow: 1;
min-height: 136px;
background: _rel("../../img/overlay/gameover.svg") bottom center / contain no-repeat;
@media (min-height: 500px) {
min-height: 218px;
}
&.share {
background-image: _rel("../../img/overlay/share.svg");
}
&.pause {
background-image: _rel("../../img/overlay/pause.svg");
}
h1 {
text-align: center;
color: white;
line-height: 42px;
font-size: 42px;
font-weight: 800;
letter-spacing: -2px;
margin: 0;
text-shadow: 1px 2px 0 rgba(0,0,0,.13);
font-family: Lobster;
}
&:not(.gameover) h1 {
display: none;
}
flex-grow: 1;
min-height: 136px;
background: _rel('../../img/overlay/gameover.svg') bottom center / contain
no-repeat;
@media (min-height: 500px) {
min-height: 218px;
}
&.share {
background-image: _rel('../../img/overlay/share.svg');
}
&.pause {
background-image: _rel('../../img/overlay/pause.svg');
}
h1 {
text-align: center;
color: white;
line-height: 42px;
font-size: 42px;
font-weight: 800;
letter-spacing: -2px;
margin: 0;
text-shadow: 1px 2px 0 rgba(0, 0, 0, 0.13);
font-family: Lobster;
}
&:not(.gameover) h1 {
display: none;
}
}
.below {
display: flex;
align-items: space-around;
justify-content: center;
margin: 1em;
transition: opacity $duration, transform $duration;
::slotted(santa-card) {
pointer-events: auto;
margin: 8px;
width: 200px;
height: 120px;