[
  {
    "path": ".babelrc",
    "content": "{\n  \"presets\": [\n    [\"env\", {\n      \"modules\": false,\n      \"targets\": {\n        \"browsers\": [\"> 1%\", \"last 2 versions\", \"not ie <= 8\"]\n      }\n    }],\n    \"stage-2\"\n  ],\n  \"plugins\": [\"transform-vue-jsx\", \"transform-runtime\"],\n  \"env\": {\n    \"test\": {\n      \"presets\": [\"env\", \"stage-2\"],\n      \"plugins\": [\"transform-vue-jsx\", \"istanbul\"]\n    }\n  }\n}\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n"
  },
  {
    "path": ".eslintignore",
    "content": "/build/\n/config/\n/dist/\n/*.js\n/test/unit/coverage/\n"
  },
  {
    "path": ".eslintrc.js",
    "content": "// https://eslint.org/docs/user-guide/configuring\n\nmodule.exports = {\n  root: true,\n  parserOptions: {\n    parser: 'babel-eslint'\n  },\n  env: {\n    browser: true,\n  },\n  extends: [\n    // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention\n    // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.\n    'plugin:vue/essential', \n    // https://github.com/standard/standard/blob/master/docs/RULES-en.md\n    'standard'\n  ],\n  // required to lint *.vue files\n  plugins: [\n    'vue'\n  ],\n  // add your custom rules here\n  rules: {\n    // allow async-await\n    'generator-star-spacing': 'off',\n    // allow debugger during development\n    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'\n  }\n}\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\nnode_modules/\ndist/\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n/test/unit/coverage/\n/test/e2e/reports/\nselenium-debug.log\n\n# Editor directories and files\n.idea\n.vscode\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n"
  },
  {
    "path": ".postcssrc.js",
    "content": "// https://github.com/michael-ciniawsky/postcss-load-config\n\nmodule.exports = {\n  \"plugins\": {\n    \"postcss-import\": {},\n    \"postcss-url\": {},\n    // to edit target browsers: use \"browserslist\" field in package.json\n    \"autoprefixer\": {}\n  }\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018 Rebble (pebble-dev)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Rebble Store for pebble\n\nThe Rebble Store is a Pebble Appstore replacement.\n\nIf you want to contribute join us on the [Pebble Dev Discord server](http://discord.gg/aRUAYFN), then head to `#appstore`.\n\nThis is the Rebble replacement for the Pebble app store. This project is under active development, though the eventual goal is to reach feature parity with the current Pebble smartwatch app store.\n\nThis project is built with [VueJS 2](https://vuejs.org/), with webpack scripts included for debugging, hot-reload, and production builds. More information on the Vue webpack build scripts can be found [here](https://github.com/vuejs-templates/webpack).\n\n## Backend/API\n\nThis project has a separate backend/api that's currently written in Python. It can be found [here](https://github.com/pebble-dev/rebble-appstore-api).\n\nIt's not necessary to run the API locally unless also developing for the API. The frontend points to the production API by default.\n\n## Installing\n\nIf you want to run a local version you will also need to run the backend.\n\n``` bash\n# install dependencies\nnpm install\n\n# serve with hot reload at localhost:8081\nnpm run dev\n\n# build for production with minification\nnpm run build\n\n# run unit tests\nnpm run unit\n\n# run e2e tests\nnpm run e2e\n\n# run all tests\nnpm test\n```\n"
  },
  {
    "path": "build/build.js",
    "content": "'use strict'\nrequire('./check-versions')()\n\nprocess.env.NODE_ENV = 'production'\n\nconst ora = require('ora')\nconst rm = require('rimraf')\nconst path = require('path')\nconst chalk = require('chalk')\nconst webpack = require('webpack')\nconst config = require('../config')\nconst webpackConfig = require('./webpack.prod.conf')\n\nconst spinner = ora('building for production...')\nspinner.start()\n\nrm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {\n  if (err) throw err\n  webpack(webpackConfig, (err, stats) => {\n    spinner.stop()\n    if (err) throw err\n    process.stdout.write(stats.toString({\n      colors: true,\n      modules: false,\n      children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.\n      chunks: false,\n      chunkModules: false\n    }) + '\\n\\n')\n\n    if (stats.hasErrors()) {\n      console.log(chalk.red('  Build failed with errors.\\n'))\n      process.exit(1)\n    }\n\n    console.log(chalk.cyan('  Build complete.\\n'))\n    console.log(chalk.yellow(\n      '  Tip: built files are meant to be served over an HTTP server.\\n' +\n      '  Opening index.html over file:// won\\'t work.\\n'\n    ))\n  })\n})\n"
  },
  {
    "path": "build/check-versions.js",
    "content": "'use strict'\nconst chalk = require('chalk')\nconst semver = require('semver')\nconst packageConfig = require('../package.json')\nconst shell = require('shelljs')\n\nfunction exec (cmd) {\n  return require('child_process').execSync(cmd).toString().trim()\n}\n\nconst versionRequirements = [\n  {\n    name: 'node',\n    currentVersion: semver.clean(process.version),\n    versionRequirement: packageConfig.engines.node\n  }\n]\n\nif (shell.which('npm')) {\n  versionRequirements.push({\n    name: 'npm',\n    currentVersion: exec('npm --version'),\n    versionRequirement: packageConfig.engines.npm\n  })\n}\n\nmodule.exports = function () {\n  const warnings = []\n\n  for (let i = 0; i < versionRequirements.length; i++) {\n    const mod = versionRequirements[i]\n\n    if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {\n      warnings.push(mod.name + ': ' +\n        chalk.red(mod.currentVersion) + ' should be ' +\n        chalk.green(mod.versionRequirement)\n      )\n    }\n  }\n\n  if (warnings.length) {\n    console.log('')\n    console.log(chalk.yellow('To use this template, you must update following to modules:'))\n    console.log()\n\n    for (let i = 0; i < warnings.length; i++) {\n      const warning = warnings[i]\n      console.log('  ' + warning)\n    }\n\n    console.log()\n    process.exit(1)\n  }\n}\n"
  },
  {
    "path": "build/dev-client.js",
    "content": "/* eslint-disable */\nrequire('eventsource-polyfill')\nvar hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')\n\nhotClient.subscribe(function (event) {\n  if (event.action === 'reload') {\n    window.location.reload()\n  }\n})\n"
  },
  {
    "path": "build/dev-server.js",
    "content": "require('./check-versions')()\nvar config = require('../config')\nif (!process.env.NODE_ENV) process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)\nvar path = require('path')\nvar express = require('express')\nvar webpack = require('webpack')\nvar opn = require('opn')\nvar proxyMiddleware = require('http-proxy-middleware')\nvar webpackConfig = process.env.NODE_ENV === 'testing'\n  ? require('./webpack.prod.conf')\n  : require('./webpack.dev.conf')\n\n// default port where dev server listens for incoming traffic\nvar port = process.env.PORT || config.dev.port\n// Define HTTP proxies to your custom API backend\n// https://github.com/chimurai/http-proxy-middleware\nvar proxyTable = config.dev.proxyTable\n\nvar app = express()\nvar compiler = webpack(webpackConfig)\n\nvar devMiddleware = require('webpack-dev-middleware')(compiler, {\n  publicPath: webpackConfig.output.publicPath,\n  stats: {\n    colors: true,\n    chunks: false\n  }\n})\n\nvar hotMiddleware = require('webpack-hot-middleware')(compiler)\n// force page reload when html-webpack-plugin template changes\ncompiler.plugin('compilation', function (compilation) {\n  compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {\n    hotMiddleware.publish({ action: 'reload' })\n    cb()\n  })\n})\n\n// proxy api requests\nObject.keys(proxyTable).forEach(function (context) {\n  var options = proxyTable[context]\n  if (typeof options === 'string') {\n    options = { target: options }\n  }\n  app.use(proxyMiddleware(context, options))\n})\n\n// handle fallback for HTML5 history API\napp.use(require('connect-history-api-fallback')())\n\n// serve webpack bundle output\napp.use(devMiddleware)\n\n// enable hot-reload and state-preserving\n// compilation error display\napp.use(hotMiddleware)\n\n// serve pure static assets\nvar staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)\napp.use(staticPath, express.static('./static'))\n\nmodule.exports = app.listen(port, function (err) {\n  if (err) {\n    console.log(err)\n    return\n  }\n  var uri = 'http://localhost:' + port\n  console.log('Listening at ' + uri + '\\n')\n\n  // when env is testing, don't need open it\n  if (process.env.NODE_ENV !== 'testing') {\n    opn(uri)\n  }\n})\n"
  },
  {
    "path": "build/sprite_module.conf.js",
    "content": "import BrowserSprite from 'svg-baker-runtime/browser-sprite';\nimport domready from 'domready'\n\nconst sprite = new BrowserSprite();\ndomready(() => sprite.mount('#svgContainer'))\n\nexport default sprite;"
  },
  {
    "path": "build/utils.js",
    "content": "'use strict'\nconst path = require('path')\nconst config = require('../config')\nconst ExtractTextPlugin = require('extract-text-webpack-plugin')\nconst packageConfig = require('../package.json')\n\nexports.assetsPath = function (_path) {\n  const assetsSubDirectory = process.env.NODE_ENV === 'production'\n    ? config.build.assetsSubDirectory\n    : config.dev.assetsSubDirectory\n\n  return path.posix.join(assetsSubDirectory, _path)\n}\n\nexports.cssLoaders = function (options) {\n  options = options || {}\n\n  const cssLoader = {\n    loader: 'css-loader',\n    options: {\n      sourceMap: options.sourceMap\n    }\n  }\n\n  const postcssLoader = {\n    loader: 'postcss-loader',\n    options: {\n      sourceMap: options.sourceMap\n    }\n  }\n\n  // generate loader string to be used with extract text plugin\n  function generateLoaders (loader, loaderOptions) {\n    const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]\n\n    if (loader) {\n      loaders.push({\n        loader: loader + '-loader',\n        options: Object.assign({}, loaderOptions, {\n          sourceMap: options.sourceMap\n        })\n      })\n    }\n\n    // Extract CSS when that option is specified\n    // (which is the case during production build)\n    if (options.extract) {\n      return ExtractTextPlugin.extract({\n        use: loaders,\n        fallback: 'vue-style-loader'\n      })\n    } else {\n      return ['vue-style-loader'].concat(loaders)\n    }\n  }\n\n  // https://vue-loader.vuejs.org/en/configurations/extract-css.html\n  return {\n    css: generateLoaders(),\n    postcss: generateLoaders(),\n    less: generateLoaders('less'),\n    sass: generateLoaders('sass', { indentedSyntax: true }),\n    scss: generateLoaders('sass').concat(\n      {\n        loader: 'sass-resources-loader',\n        options: {\n          resources:  [path.resolve(__dirname, '../static/css/_variables.scss')]\n        }\n      }\n    ),\n    stylus: generateLoaders('stylus'),\n    styl: generateLoaders('stylus')\n  }\n}\n\n// Generate loaders for standalone style files (outside of .vue)\nexports.styleLoaders = function (options) {\n  const output = []\n  const loaders = exports.cssLoaders(options)\n\n  for (const extension in loaders) {\n    const loader = loaders[extension]\n    output.push({\n      test: new RegExp('\\\\.' + extension + '$'),\n      use: loader\n    })\n  }\n\n  return output\n}\n\nexports.createNotifierCallback = () => {\n  const notifier = require('node-notifier')\n\n  return (severity, errors) => {\n    if (severity !== 'error') return\n\n    const error = errors[0]\n    const filename = error.file && error.file.split('!').pop()\n\n    notifier.notify({\n      title: packageConfig.name,\n      message: severity + ': ' + error.name,\n      subtitle: filename || '',\n      icon: path.join(__dirname, 'logo.png')\n    })\n  }\n}\n"
  },
  {
    "path": "build/vue-loader.conf.js",
    "content": "'use strict'\nconst utils = require('./utils')\nconst config = require('../config')\nconst isProduction = process.env.NODE_ENV === 'production'\nconst sourceMapEnabled = isProduction\n  ? config.build.productionSourceMap\n  : config.dev.cssSourceMap\n\nmodule.exports = {\n  loaders: utils.cssLoaders({\n    sourceMap: sourceMapEnabled,\n    extract: isProduction\n  }),\n  cssSourceMap: sourceMapEnabled,\n  cacheBusting: config.dev.cacheBusting,\n  transformToRequire: {\n    video: ['src', 'poster'],\n    source: 'src',\n    img: 'src',\n    image: 'xlink:href'\n  }\n}\n"
  },
  {
    "path": "build/webpack.base.conf.js",
    "content": "'use strict'\nconst path = require('path')\nconst utils = require('./utils')\nconst config = require('../config')\nconst vueLoaderConfig = require('./vue-loader.conf')\n\n\nfunction resolve (dir) {\n  return path.join(__dirname, '..', dir)\n}\n\nconst createLintingRule = () => ({\n  test: /\\.(js|vue)$/,\n  loader: 'eslint-loader',\n  enforce: 'pre',\n  include: [resolve('src'), resolve('test')],\n  options: {\n    formatter: require('eslint-friendly-formatter'),\n    emitWarning: !config.dev.showEslintErrorsInOverlay\n  }\n})\n\nmodule.exports = {\n  context: path.resolve(__dirname, '../'),\n  entry: {\n    app: './src/main.js'\n  },\n  output: {\n    path: config.build.assetsRoot,\n    filename: '[name].js',\n    publicPath: process.env.NODE_ENV === 'production'\n      ? config.build.assetsPublicPath\n      : config.dev.assetsPublicPath\n  },\n  resolve: {\n    extensions: ['.js', '.vue', '.json'],\n    alias: {\n      'vue$': 'vue/dist/vue.esm.js',\n      '@': resolve('src'),\n    }\n  },\n  module: {\n    rules: [\n      ...(config.dev.useEslint ? [createLintingRule()] : []),\n      {\n        test: /\\.scss$/,\n        use: [\n          {\n            loader: \"style-loader\" // creates style nodes from JS strings\n          },\n          {\n            loader: \"css-loader\" // translates CSS into CommonJS\n          },\n          {\n            loader: \"sass-loader\" // compiles Sass to CSS\n          }\n          ]\n      },\n      {\n        test: /\\.vue$/,\n        loader: 'vue-loader',\n        options: vueLoaderConfig\n      },\n      {\n        test: /\\.js$/,\n        loader: 'babel-loader',\n        include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]\n      },\n      {\n        test: /\\.(svg)$/,\n        loader: 'svg-sprite-loader',\n        include: [resolve('src/assets/svg')],\n        options: {\n          symbolId: 'icon[name]',\n          spriteModule: resolve('build/sprite_module.conf.js')\n        }\n      },\n      {\n        test: /\\.(png|jpe?g|gif|svg)(\\?.*)?$/,\n        exclude: [resolve('src/assets/svg')],\n        use:[\n          'file-loader',\n        ]\n      },\n      {\n        test: /\\.(mp4|webm|ogg|mp3|wav|flac|aac)(\\?.*)?$/,\n        loader: 'url-loader',\n        options: {\n          limit: 10000,\n          name: utils.assetsPath('media/[name].[hash:7].[ext]')\n        }\n      },\n      {\n        test: /\\.(woff2?|eot|ttf|otf)(\\?.*)?$/,\n        loader: 'url-loader',\n        options: {\n          limit: 10000,\n          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')\n        }\n      }\n    ]\n  },\n  node: {\n      // prevent webpack from injecting useless setImmediate polyfill because Vue\n      // source contains it (although only uses it if it's native).\n      setImmediate: false,\n      // prevent webpack from injecting mocks to Node native modules\n      // that does not make sense for the client\n      dgram: 'empty',\n      fs: 'empty',\n      net: 'empty',\n      tls: 'empty',\n      child_process: 'empty'\n  }\n}\n"
  },
  {
    "path": "build/webpack.dev.conf.js",
    "content": "'use strict'\nconst utils = require('./utils')\nconst webpack = require('webpack')\nconst config = require('../config')\nconst merge = require('webpack-merge')\nconst path = require('path')\nconst baseWebpackConfig = require('./webpack.base.conf')\nconst CopyWebpackPlugin = require('copy-webpack-plugin')\nconst HtmlWebpackPlugin = require('html-webpack-plugin')\nconst FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')\nconst portfinder = require('portfinder')\n\nconst HOST = process.env.HOST\nconst PORT = process.env.PORT && Number(process.env.PORT)\n\nconst devWebpackConfig = merge(baseWebpackConfig, {\n  module: {\n    rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })\n  },\n  // cheap-module-eval-source-map is faster for development\n  devtool: config.dev.devtool,\n\n  // these devServer options should be customized in /config/index.js\n  devServer: {\n    clientLogLevel: 'warning',\n    historyApiFallback: {\n      rewrites: [\n        { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },\n      ],\n    },\n    hot: true,\n    contentBase: false, // since we use CopyWebpackPlugin.\n    compress: true,\n    host: HOST || config.dev.host,\n    port: PORT || config.dev.port,\n    open: config.dev.autoOpenBrowser,\n    overlay: config.dev.errorOverlay\n      ? { warnings: false, errors: true }\n      : false,\n    publicPath: config.dev.assetsPublicPath,\n    proxy: config.dev.proxyTable,\n    quiet: true, // necessary for FriendlyErrorsPlugin\n    watchOptions: {\n      poll: config.dev.poll,\n    }\n  },\n  plugins: [\n    new webpack.DefinePlugin({\n      'process.env': require('../config/dev.env')\n    }),\n    new webpack.HotModuleReplacementPlugin(),\n    new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.\n    new webpack.NoEmitOnErrorsPlugin(),\n    // https://github.com/ampedandwired/html-webpack-plugin\n    new HtmlWebpackPlugin({\n      filename: 'index.html',\n      template: 'index.html',\n      inject: true\n    }),\n    // copy custom static assets\n    new CopyWebpackPlugin([\n      {\n        from: path.resolve(__dirname, '../static'),\n        to: config.dev.assetsSubDirectory,\n        ignore: ['.*']\n      }\n    ])\n  ]\n})\n\nmodule.exports = new Promise((resolve, reject) => {\n  portfinder.basePort = process.env.PORT || config.dev.port\n  portfinder.getPort((err, port) => {\n    if (err) {\n      reject(err)\n    } else {\n      // publish the new Port, necessary for e2e tests\n      process.env.PORT = port\n      // add port to devServer config\n      devWebpackConfig.devServer.port = port\n\n      // Add FriendlyErrorsPlugin\n      devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({\n        compilationSuccessInfo: {\n          messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],\n        },\n        onErrors: config.dev.notifyOnErrors\n        ? utils.createNotifierCallback()\n        : undefined\n      }))\n\n      resolve(devWebpackConfig)\n    }\n  })\n})\n"
  },
  {
    "path": "build/webpack.prod.conf.js",
    "content": "'use strict'\nconst path = require('path')\nconst utils = require('./utils')\nconst webpack = require('webpack')\nconst config = require('../config')\nconst merge = require('webpack-merge')\nconst baseWebpackConfig = require('./webpack.base.conf')\nconst CopyWebpackPlugin = require('copy-webpack-plugin')\nconst HtmlWebpackPlugin = require('html-webpack-plugin')\nconst ExtractTextPlugin = require('extract-text-webpack-plugin')\nconst OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')\nconst UglifyJsPlugin = require('uglifyjs-webpack-plugin')\n\nconst env = process.env.NODE_ENV === 'testing'\n  ? require('../config/test.env')\n  : require('../config/prod.env')\n\nconst webpackConfig = merge(baseWebpackConfig, {\n  module: {\n    rules: utils.styleLoaders({\n      sourceMap: config.build.productionSourceMap,\n      extract: true,\n      usePostCSS: true\n    })\n  },\n  devtool: config.build.productionSourceMap ? config.build.devtool : false,\n  output: {\n    path: config.build.assetsRoot,\n    filename: utils.assetsPath('js/[name].[chunkhash].js'),\n    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')\n  },\n  plugins: [\n    // http://vuejs.github.io/vue-loader/en/workflow/production.html\n    new webpack.DefinePlugin({\n      'process.env': env\n    }),\n    new UglifyJsPlugin({\n      uglifyOptions: {\n        compress: {\n          warnings: false\n        }\n      },\n      sourceMap: config.build.productionSourceMap,\n      parallel: true\n    }),\n    // extract css into its own file\n    new ExtractTextPlugin({\n      filename: utils.assetsPath('css/[name].[contenthash].css'),\n      // Setting the following option to `false` will not extract CSS from codesplit chunks.\n      // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.\n      // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, \n      // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110\n      allChunks: true,\n    }),\n    // Compress extracted CSS. We are using this plugin so that possible\n    // duplicated CSS from different components can be deduped.\n    new OptimizeCSSPlugin({\n      cssProcessorOptions: config.build.productionSourceMap\n        ? { safe: true, map: { inline: false } }\n        : { safe: true }\n    }),\n    // generate dist index.html with correct asset hash for caching.\n    // you can customize output by editing /index.html\n    // see https://github.com/ampedandwired/html-webpack-plugin\n    new HtmlWebpackPlugin({\n      filename: process.env.NODE_ENV === 'testing'\n        ? 'index.html'\n        : config.build.index,\n      template: 'index.html',\n      inject: true,\n      minify: {\n        removeComments: true,\n        collapseWhitespace: true,\n        removeAttributeQuotes: true\n        // more options:\n        // https://github.com/kangax/html-minifier#options-quick-reference\n      },\n      // necessary to consistently work with multiple chunks via CommonsChunkPlugin\n      chunksSortMode: 'dependency'\n    }),\n    // keep module.id stable when vendor modules does not change\n    new webpack.HashedModuleIdsPlugin(),\n    // enable scope hoisting\n    new webpack.optimize.ModuleConcatenationPlugin(),\n    // split vendor js into its own file\n    new webpack.optimize.CommonsChunkPlugin({\n      name: 'vendor',\n      minChunks (module) {\n        // any required modules inside node_modules are extracted to vendor\n        return (\n          module.resource &&\n          /\\.js$/.test(module.resource) &&\n          module.resource.indexOf(\n            path.join(__dirname, '../node_modules')\n          ) === 0\n        )\n      }\n    }),\n    // extract webpack runtime and module manifest to its own file in order to\n    // prevent vendor hash from being updated whenever app bundle is updated\n    new webpack.optimize.CommonsChunkPlugin({\n      name: 'manifest',\n      minChunks: Infinity\n    }),\n    // This instance extracts shared chunks from code splitted chunks and bundles them\n    // in a separate chunk, similar to the vendor chunk\n    // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk\n    new webpack.optimize.CommonsChunkPlugin({\n      name: 'app',\n      async: 'vendor-async',\n      children: true,\n      minChunks: 3\n    }),\n\n    // copy custom static assets\n    new CopyWebpackPlugin([\n      {\n        from: path.resolve(__dirname, '../static'),\n        to: config.build.assetsSubDirectory,\n        ignore: ['.*']\n      }\n    ])\n  ]\n})\n\nif (config.build.productionGzip) {\n  const CompressionWebpackPlugin = require('compression-webpack-plugin')\n\n  webpackConfig.plugins.push(\n    new CompressionWebpackPlugin({\n      asset: '[path].gz[query]',\n      algorithm: 'gzip',\n      test: new RegExp(\n        '\\\\.(' +\n        config.build.productionGzipExtensions.join('|') +\n        ')$'\n      ),\n      threshold: 10240,\n      minRatio: 0.8\n    })\n  )\n}\n\nif (config.build.bundleAnalyzerReport) {\n  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin\n  webpackConfig.plugins.push(new BundleAnalyzerPlugin())\n}\n\nmodule.exports = webpackConfig\n"
  },
  {
    "path": "build/webpack.test.conf.js",
    "content": "'use strict'\n// This is the webpack config used for unit tests.\n\nconst utils = require('./utils')\nconst webpack = require('webpack')\nconst merge = require('webpack-merge')\nconst baseWebpackConfig = require('./webpack.base.conf')\n\nconst webpackConfig = merge(baseWebpackConfig, {\n  // use inline sourcemap for karma-sourcemap-loader\n  module: {\n    rules: utils.styleLoaders()\n  },\n  devtool: '#inline-source-map',\n  resolveLoader: {\n    alias: {\n      // necessary to to make lang=\"scss\" work in test when using vue-loader's ?inject option\n      // see discussion at https://github.com/vuejs/vue-loader/issues/724\n      'scss-loader': 'sass-loader'\n    }\n  },\n  plugins: [\n    new webpack.DefinePlugin({\n      'process.env': require('../config/test.env')\n    })\n  ]\n})\n\n// no need for app entry during tests\ndelete webpackConfig.entry\n\nmodule.exports = webpackConfig\n"
  },
  {
    "path": "config/dev.env.js",
    "content": "'use strict'\nconst merge = require('webpack-merge')\nconst prodEnv = require('./prod.env')\n\nmodule.exports = merge(prodEnv, {\n  NODE_ENV: '\"development\"'\n})\n"
  },
  {
    "path": "config/index.js",
    "content": "'use strict'\n// Template version: 1.3.1\n// see http://vuejs-templates.github.io/webpack for documentation.\n\nconst path = require('path')\n\nmodule.exports = {\n  dev: {\n\n    // Paths\n    assetsSubDirectory: 'static',\n    assetsPublicPath: '/',\n    proxyTable: {},\n\n    // Various Dev Server settings\n    host: '0.0.0.0', // can be overwritten by process.env.HOST\n    port: 8081, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined\n    autoOpenBrowser: false,\n    errorOverlay: true,\n    notifyOnErrors: true,\n    poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-\n\n    // Use Eslint Loader?\n    // If true, your code will be linted during bundling and\n    // linting errors and warnings will be shown in the console.\n    useEslint: true,\n    // If true, eslint errors and warnings will also be shown in the error overlay\n    // in the browser.\n    showEslintErrorsInOverlay: false,\n\n    /**\n     * Source Maps\n     */\n\n    // https://webpack.js.org/configuration/devtool/#development\n    devtool: 'cheap-module-eval-source-map',\n\n    // If you have problems debugging vue-files in devtools,\n    // set this to false - it *may* help\n    // https://vue-loader.vuejs.org/en/options.html#cachebusting\n    cacheBusting: true,\n\n    cssSourceMap: true\n  },\n\n  build: {\n    // Template for index.html\n    index: path.resolve(__dirname, '../dist/index.html'),\n\n    // Paths\n    assetsRoot: path.resolve(__dirname, '../dist'),\n    assetsSubDirectory: 'static',\n    assetsPublicPath: '/',\n\n    /**\n     * Source Maps\n     */\n\n    productionSourceMap: true,\n    // https://webpack.js.org/configuration/devtool/#production\n    devtool: '#source-map',\n\n    // Gzip off by default as many popular static hosts such as\n    // Surge or Netlify already gzip all static assets for you.\n    // Before setting to `true`, make sure to:\n    // npm install --save-dev compression-webpack-plugin\n    productionGzip: false,\n    productionGzipExtensions: ['js', 'css'],\n\n    // Run the build command with an extra argument to\n    // View the bundle analyzer report after build finishes:\n    // `npm run build --report`\n    // Set to `true` or `false` to always turn it on or off\n    bundleAnalyzerReport: process.env.npm_config_report\n  }\n}\n"
  },
  {
    "path": "config/prod.env.js",
    "content": "'use strict'\nmodule.exports = {\n  NODE_ENV: '\"production\"'\n}\n"
  },
  {
    "path": "config/test.env.js",
    "content": "'use strict'\nconst merge = require('webpack-merge')\nconst devEnv = require('./dev.env')\n\nmodule.exports = merge(devEnv, {\n  NODE_ENV: '\"testing\"'\n})\n"
  },
  {
    "path": "index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1,shrink-to-fit=no\">\n    <meta http-equiv=\"x-ua-compatible\" content=\"ie=edge\">\n\n    <title>Rebble App Store</title>\n\n    <!-- Site icons and WebApp metadata -->\n    <link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"static/apple-touch-icon.png\">\n    <link rel=\"icon\" type=\"image/png\" href=\"static/favicon-32x32.png\" sizes=\"32x32\">\n    <link rel=\"icon\" type=\"image/png\" href=\"static/favicon-16x16.png\" sizes=\"16x16\">\n    <link rel=\"manifest\" href=\"static/manifest.json\">\n    <link rel=\"mask-icon\" href=\"static/safari-pinned-tab.svg\" color=\"#373a3c\">\n    <link rel=\"shortcut icon\" href=\"static/favicon.ico\">\n    <meta name=\"apple-mobile-web-app-title\" content=\"Rebble Store\">\n    <meta name=\"application-name\" content=\"Rebble Store\">\n    <meta name=\"msapplication-config\" content=\"static/browserconfig.xml\">\n    <meta name=\"theme-color\" content=\"#ffffff\">\n\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <!-- built files will be auto injected -->\n  </body>\n</html>\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"rebble-app-store\",\n  \"version\": \"1.0.0-beta.3\",\n  \"description\": \"Rebble Store, a pebble app store replacement.\",\n  \"author\": \"Rebble Web Services Team\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"webpack-dev-server --inline --progress --config build/webpack.dev.conf.js\",\n    \"start\": \"npm run dev\",\n    \"unit\": \"cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run\",\n    \"e2e\": \"node test/e2e/runner.js\",\n    \"test\": \"npm run unit && npm run e2e\",\n    \"lint\": \"eslint --ext .js,.vue src test/unit test/e2e/specs\",\n    \"build\": \"node build/build.js\"\n  },\n  \"dependencies\": {\n    \"algoliasearch\": \"^3.33.0\",\n    \"bootstrap-vue\": \"^2.0.0-rc.27\",\n    \"es6-promise\": \"^4.2.5\",\n    \"vue\": \"^2.6.10\",\n    \"vue-cookie\": \"^1.1.4\",\n    \"vue-dragscroll\": \"^1.10.0\",\n    \"vue-instantsearch\": \"^2.3.0\",\n    \"vue-router\": \"^3.0.7\",\n    \"vuex\": \"^3.1.1\",\n    \"vuex-pathify\": \"^1.2.4\"\n  },\n  \"devDependencies\": {\n    \"autoprefixer\": \"^7.1.2\",\n    \"babel-core\": \"^6.26.3\",\n    \"babel-eslint\": \"^8.2.1\",\n    \"babel-helper-vue-jsx-merge-props\": \"^2.0.3\",\n    \"babel-loader\": \"^7.1.1\",\n    \"babel-plugin-istanbul\": \"^4.1.6\",\n    \"babel-plugin-syntax-jsx\": \"^6.18.0\",\n    \"babel-plugin-transform-runtime\": \"^6.22.0\",\n    \"babel-plugin-transform-vue-jsx\": \"^3.7.0\",\n    \"babel-preset-env\": \"^1.7.0\",\n    \"babel-preset-stage-2\": \"^6.22.0\",\n    \"babel-register\": \"^6.22.0\",\n    \"chai\": \"^4.2.0\",\n    \"chalk\": \"^2.4.1\",\n    \"chromedriver\": \"^2.44.1\",\n    \"copy-webpack-plugin\": \"^4.6.0\",\n    \"cross-env\": \"^5.2.0\",\n    \"cross-spawn\": \"^5.0.1\",\n    \"css-loader\": \"^0.28.0\",\n    \"domready\": \"^1.0.8\",\n    \"eslint\": \"^4.15.0\",\n    \"eslint-config-standard\": \"^10.2.1\",\n    \"eslint-friendly-formatter\": \"^3.0.0\",\n    \"eslint-loader\": \"^1.7.1\",\n    \"eslint-plugin-import\": \"^2.14.0\",\n    \"eslint-plugin-node\": \"^5.2.0\",\n    \"eslint-plugin-promise\": \"^3.4.0\",\n    \"eslint-plugin-standard\": \"^3.0.1\",\n    \"eslint-plugin-vue\": \"^4.0.0\",\n    \"extract-text-webpack-plugin\": \"^3.0.0\",\n    \"file-loader\": \"^1.1.4\",\n    \"friendly-errors-webpack-plugin\": \"^1.7.0\",\n    \"html-webpack-plugin\": \"^2.30.1\",\n    \"inject-loader\": \"^3.0.0\",\n    \"karma\": \"^1.4.1\",\n    \"karma-coverage\": \"^1.1.2\",\n    \"karma-mocha\": \"^1.3.0\",\n    \"karma-phantomjs-launcher\": \"^1.0.2\",\n    \"karma-phantomjs-shim\": \"^1.4.0\",\n    \"karma-sinon-chai\": \"^1.3.1\",\n    \"karma-sourcemap-loader\": \"^0.3.7\",\n    \"karma-spec-reporter\": \"0.0.31\",\n    \"karma-webpack\": \"^2.0.2\",\n    \"mocha\": \"^3.2.0\",\n    \"nightwatch\": \"^0.9.12\",\n    \"node-notifier\": \"^5.3.0\",\n    \"node-sass\": \"^4.11.0\",\n    \"optimize-css-assets-webpack-plugin\": \"^3.2.0\",\n    \"ora\": \"^1.2.0\",\n    \"phantomjs-prebuilt\": \"^2.1.14\",\n    \"portfinder\": \"^1.0.20\",\n    \"postcss-import\": \"^11.0.0\",\n    \"postcss-loader\": \"^2.0.8\",\n    \"postcss-url\": \"^7.2.1\",\n    \"rimraf\": \"^2.6.0\",\n    \"sass-loader\": \"^6.0.6\",\n    \"sass-resources-loader\": \"^1.3.2\",\n    \"selenium-server\": \"^3.141.59\",\n    \"semver\": \"^5.6.0\",\n    \"shelljs\": \"^0.7.6\",\n    \"sinon\": \"^4.0.0\",\n    \"sinon-chai\": \"^2.8.0\",\n    \"svg-sprite-loader\": \"^3.9.0\",\n    \"uglifyjs-webpack-plugin\": \"^1.1.1\",\n    \"url-loader\": \"^0.5.8\",\n    \"vue-content-loading\": \"^1.5.3\",\n    \"vue-images-loaded\": \"^1.1.2\",\n    \"vue-loader\": \"^13.3.0\",\n    \"vue-resource\": \"^1.5.1\",\n    \"vue-style-loader\": \"^3.0.1\",\n    \"vue-template-compiler\": \"^2.5.21\",\n    \"webpack\": \"^3.6.0\",\n    \"webpack-bundle-analyzer\": \"^2.9.0\",\n    \"webpack-dev-server\": \"^2.9.1\",\n    \"webpack-merge\": \"^4.1.5\"\n  },\n  \"engines\": {\n    \"node\": \">= 6.0.0\",\n    \"npm\": \">= 3.0.0\"\n  },\n  \"browserslist\": [\n    \"> 1%\",\n    \"last 2 versions\",\n    \"not ie <= 8\"\n  ]\n}\n"
  },
  {
    "path": "src/404.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <!-- Required meta tags always come first -->\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n  <meta http-equiv=\"x-ua-compatible\" content=\"ie=edge\">\n  <title>Rebble Store</title>\n\n    <!-- Site icons -->\n    <link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"assets/apple-touch-icon.png\">\n    <link rel=\"icon\" type=\"image/png\" href=\"assets/favicon-32x32.png\" sizes=\"32x32\">\n    <link rel=\"icon\" type=\"image/png\" href=\"assets/favicon-16x16.png\" sizes=\"16x16\">\n    <link rel=\"manifest\" href=\"assets/manifest.json\">\n    <link rel=\"mask-icon\" href=\"assets/safari-pinned-tab.svg\" color=\"#373a3c\">\n    <link rel=\"shortcut icon\" href=\"assets/favicon.ico\">\n    <meta name=\"apple-mobile-web-app-title\" content=\"Rebble Store\">\n    <meta name=\"application-name\" content=\"Rebble Store\">\n    <meta name=\"msapplication-config\" content=\"assets/browserconfig.xml\">\n    <meta name=\"theme-color\" content=\"#ffffff\">\n    \n  <!-- Bootstrap CSS -->\n  <link rel=\"stylesheet\" href=\"css/bootstrap.css\">\n  <link rel=\"stylesheet\" href=\"css/font-awesome.min.css\">\n  <link rel=\"stylesheet\" href=\"css/main.css\">\n</head>\n\n<body>\n  <div class=\"flex-content\">\n    <nav class=\"navbar navbar-fixed-top navbar-dark bg-inverse text-sm-center translucent\">\n      <div class=\"navbar-container\">\n\n        <a class=\"navbar-brand\" href=\"index.html\">Rebble Store <small>for&nbsp;<div class=\"pebble\">pebble</div></small></a>\n\n        <div class=\"navbar__items right\">\n          <button class=\"navbar-toggler\" type=\"button\" data-toggle=\"collapse\" data-target=\"#categorySelector\" aria-controls=\"categorySelector\" aria-expanded=\"false\" aria-label=\"Toggle navigation\"></button>\n\n          <a class=\"search\" href=\"search.html\">\n            <svg width=\"25px\" height=\"25px\" viewBox=\"0 0 25 25\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n              <g id=\"Icons\"  stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\">\n                <g id=\"Search\" stroke=\"#a5a6a7\">\n                  <path id=\"Shape\" stroke-width=\"1.5\" class=\"st0\" fill=\"none\" d=\"M13.9,16.6l1.2-0.4c0,0,0.8-1.5,0.8-1.6c2.3,2.4,4.7,4.7,7,7.1l-2,2\n                  C18.5,21.2,16.2,18.9,13.9,16.6z\"/>\n                  <polygon id=\"Shape_1_\" stroke-width=\"2\" fill=\"none\" class=\"st1\" points=\"2.2,10.2 4.6,5 10.3,3 15.4,5.4 17.5,11 15.1,16.1 9.4,18.2 4.3,15.8 \t\t\"/>\n                  <path id=\"Line\" fill=\"none\" class=\"st2\" d=\"M11.3,6.6l2,1l1,3\" stroke-linecap=\"square\"/>\n                </g>\n              </g>\n            </svg>\n          </a>\n\n        </div>\n      </div>\n\n      <div class=\"collapse text-xs-center\" id=\"categorySelector\">\n        <div class=\"text-muted p-1\">\n          <div class=\"btn-group btn-group-lg\" role=\"group\">\n            <a href=\"index.html\" class=\"btn btn-outline-secondary\" role=\"button\">\n              <svg class=\"watchface\" width=\"25px\" height=\"25px\" viewBox=\"0 0 25 25\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n                <g id=\"Icons\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\">\n                  <g id=\"Clock\" stroke-width=\"2\" stroke=\"rgba(204,204,204,.5)\">\n                    <polygon id=\"Shape\" fill=\"none\" points=\"2 8.00285097 8 2 17 2 23 8.00285097 23 17.0255772 17 23 8 23 2 17.0255772\"></polygon>\n                    <polyline id=\"Line\" stroke-linecap=\"square\" points=\"15.5630383 14.7579965 12.040745 12.1503281 12 6\"></polyline>\n                  </g>\n                </g>\n              </svg> Watchfaces\n            </a>\n            <a href=\"apps.html\" class=\"btn btn-outline-secondary active\" role=\"button\">\n              <svg class=\"app\" width=\"25px\" height=\"25px\" viewBox=\"0 0 25 25\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n                <defs>\n                  <polygon id=\"path-1\" points=\"9 4 19 4 21 6 21 16 19 18 9 18 7 15.9960225 7 6\"></polygon>\n                  <mask id=\"mask-2\" maskContentUnits=\"userSpaceOnUse\" maskUnits=\"objectBoundingBox\" x=\"-2\" y=\"-2\" width=\"18\" height=\"18\">\n                    <rect x=\"5\" y=\"2\" width=\"18\" height=\"18\" fill=\"white\"></rect>\n                    <use xlink:href=\"#path-1\" fill=\"black\"></use>\n                  </mask>\n                </defs>\n                <g id=\"Icons\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\">\n                  <g id=\"App\">\n                    <g id=\"Rectangle-2\">\n                      <use fill=\"none\" fill-rule=\"evenodd\" xlink:href=\"#path-1\"></use>\n                      <use id=\"use2\" stroke=\"rgba(204,204,204,.5)\" mask=\"url(#mask-2)\" stroke-width=\"4\" xlink:href=\"#path-1\"></use>\n                    </g>\n                    <polyline id=\"Line\" stroke=\"rgba(204,204,204,.5)\" stroke-width=\"2\" stroke-linecap=\"square\" points=\"17 22.0005626 7 22.0005626 3 18 3 8\"></polyline>\n                  </g>\n                </g>\n              </svg>\n              Apps\n            </a>\n          </div>\n        </div>\n      </div>\n    </nav>\n    <main class=\"container text-xs-center\">\n      <section>\n        <div class=\"page-error page-error--404\">\n\n          <h2>Our pet rock has lost your page, sorry about that</h2>\n\n          <svg class=\"pet-rock-pebble\"xmlns=\"http://www.w3.org/2000/svg\" width=\"406\" height=\"199\" viewBox=\"0 0 406 199\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n            <defs>\n              <polygon id=\"a\" points=\"160.865 162.763 243.499 144.619 257.327 109.929 242.053 72.586 209.66 56.935 180.398 34.881 138.125 29.331 89.961 41.943 58.829 43.073 31.846 60.054 13.107 99.878 36.862 138.39 100.393 158.597\"/>\n              <mask id=\"d\" width=\"244.22\" height=\"133.432\" x=\"0\" y=\"0\" fill=\"white\">\n                <use xlink:href=\"#a\"/>\n              </mask>\n              <circle id=\"b\" cx=\"80.5\" cy=\"114.5\" r=\"9.5\"/>\n              <mask id=\"e\" width=\"19\" height=\"19\" x=\"0\" y=\"0\" fill=\"white\">\n                <use xlink:href=\"#b\"/>\n              </mask>\n              <circle id=\"c\" cx=\"29.5\" cy=\"101.5\" r=\"9.5\"/>\n              <mask id=\"f\" width=\"19\" height=\"19\" x=\"0\" y=\"0\" fill=\"white\">\n                <use xlink:href=\"#c\"/>\n              </mask>\n            </defs>\n            <g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(-14 3)\">\n              <g transform=\"matrix(-1 0 0 1 271 30)\">\n                <use fill=\"#D8D8D8\" stroke=\"#373A3C\" stroke-width=\"10\" mask=\"url(#d)\" transform=\"rotate(-15 135.217 96.047)\" xlink:href=\"#a\"/>\n                <polyline stroke=\"#373A3C\" stroke-width=\"2\" points=\"21 129.5 60.5 150 118.5 146.5 175 137 236.5 103 253.5 75.5\"/>\n                <circle cx=\"31.5\" cy=\"98.5\" r=\"13.5\" fill=\"#FFFFFF\" stroke=\"#373A3C\" stroke-width=\"2\"/>\n                <circle cx=\"83.5\" cy=\"110.5\" r=\"13.5\" fill=\"#FFFFFF\" stroke=\"#373A3C\" stroke-width=\"2\"/>\n                <use fill=\"#373A3C\" stroke=\"#373A3C\" stroke-width=\"2\" mask=\"url(#e)\" xlink:href=\"#b\"/>\n                <use fill=\"#373A3C\" stroke=\"#373A3C\" stroke-width=\"2\" mask=\"url(#f)\" xlink:href=\"#c\"/>\n                <circle cx=\"86\" cy=\"113\" r=\"2\" fill=\"#FFFFFF\"/>\n                <circle cx=\"34\" cy=\"100\" r=\"2\" fill=\"#FFFFFF\"/>\n              </g>\n              <polygon fill=\"#FFFFFF\" stroke=\"#373A3C\" stroke-width=\"5\" points=\"296.576 0 391.886 0 417 22.464 417 91.623 391.886 111.304 308.5 111.304 282.089 128 282.089 100.5 271 91.623 271 22.464\"/>\n              <text fill=\"#000000\" font-family=\"OpenSans-Light, Open Sans\" font-size=\"44\" font-weight=\"300\">\n                <tspan x=\"307\" y=\"71\">404</tspan>\n              </text>\n            </g>\n          </svg>\n\n          <h4>We're getting the following message {{ 404 }}</h4>\n          <div class=\"page-error_buttons\">\n            <a href=\"javascript:history.go(0)\" class=\"btn btn-outline-secondary\">Try again</a>\n            <a href=\"/\" class=\"btn btn-outline-pebble\">Back to home</a>\n          </div>\n        </div>\n\n\n      </section>\n    </main>\n  </div>\n  <footer>\n    <p>© 2016 Rebble · <a href=\"#\">Contact Us</a> · <a href=\"#\">Terms</a>\n    </p>\n    <a class=\"pull-right\" href=\"https://rebble.io/submit/\">Developer Portal</a>\n  </footer>\n  <!-- jQuery first, then Tether, then Bootstrap JS. -->\n  <script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js\" integrity=\"sha384-3ceskX3iaEnIogmQchP8opvBy3Mi7Ce34nWjpBIwVTHfGYWQS9jwHDVRnpKKHJg7\" crossorigin=\"anonymous\"></script>\n  <script src=\"https://cdnjs.cloudflare.com/ajax/libs/tether/1.3.7/js/tether.min.js\" integrity=\"sha384-XTs3FgkjiBgo8qjEjBk0tGmf3wPrWtA6coPfQDfFEY8AnYJwjalXCiosYRBIBZX8\" crossorigin=\"anonymous\"></script>\n  <script src=\"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.5/js/bootstrap.min.js\" integrity=\"sha384-BLiI7JTZm+JWlgKa0M0kGRpJbF2J8q+qreVrKBC47e3K6BW78kGLrCkeRX6I9RoK\" crossorigin=\"anonymous\"></script>\n</body>\n\n</html>\n"
  },
  {
    "path": "src/App.vue",
    "content": "<template>\n  <div id=\"app\">\n    <div v-bind:class=\"$store.state.userParameters.inApp ? 'flex-content main-in-app' : 'flex-content'\">\n      <svg-container></svg-container>\n      <navbar v-if=\"!$store.state.userParameters.inApp\"></navbar>\n      <router-view ></router-view>\n    </div>\n    <page-footer v-bind:brand=\"$store.state.userParameters.inApp\"></page-footer>\n  </div>\n</template>\n\n<script>\nimport SvgContainer from './components/SvgContainer'\nimport Home from './components/Home'\nimport Navbar from './components/Navbar'\nimport PageFooter from './components/PageFooter'\nimport { platformEnum, hardwareEnum } from './store/userParameters'\n\nexport default {\n  name: 'app',\n  components: {\n    SvgContainer,\n    Navbar,\n    Home,\n    PageFooter\n  },\n  data: function () {\n    return {\n    }\n  },\n  beforeMount () {\n    // PARAM LIST\n    // platform = 'ios' || 'android\n    // inApp: boolean\n    // devMode: boolean\n    // hardware = 'aplite' || 'basalt' || 'chalk' ...\n    // accessToken: string = rebble access token\n    // appVersion\n    let routeParameters = this.$route.query\n    // Platform refers to phone. Android or iOS.\n    if (routeParameters.platform && Object.values(platformEnum).includes(routeParameters.platform.toLowerCase())) {\n      this.$store.set('userParameters/platform', routeParameters.platform.toLowerCase())\n    }\n\n    if (routeParameters.inApp != null) {\n      this.$store.set('userParameters/inApp', routeParameters.inApp !== 'false' && routeParameters.inApp !== '0')\n    }\n\n    if (routeParameters.devMode != null) {\n      this.$store.set('userParameters/devMode', routeParameters.devMode !== 'false' && routeParameters.devMode !== '0')\n    }\n    // hardware refers to the watch. basalt, chalk, aplite, etc.\n    if (routeParameters.hardware && Object.values(hardwareEnum).includes(routeParameters.hardware.toLowerCase())) {\n      this.$store.set('userParameters/hardware', routeParameters.hardware.toLowerCase())\n    }\n\n    if (routeParameters.appVersion) {\n      this.$store.set('userParameters/appVersion', routeParameters.appVersion)\n    }\n\n    // Bearer token provided by the mobile app, needed to fetch and set app hearts\n    if (routeParameters.accessToken) {\n      this.$store.set('secure/accessToken', routeParameters.accessToken)\n    }\n  }\n}\n\n</script>\n\n<style lang=\"scss\">\n@import './static/css/_variables.scss';\n\n#app {\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  display: flex;\n  min-height: 100vh;\n  flex-direction: column;\n}\n\n.main-in-app {\n  margin-top: -43px;\n}\n\n// _fonts.scss\n// Set font-family and font-weight in here\n\n// Open Sans 300 for titles and 400 for small titles\n@import url('https://fonts.googleapis.com/css?family=Open+Sans:400,400i');\n\n//Raleway 400, 400i, 700, 700i for all of the other text\n@import url('https://fonts.googleapis.com/css?family=Raleway:400,400i,700,700i');\n\nbody {\n  font-family: 'Raleway', sans-serif;\n}\n\nh1, h2, h3, h4, h5, h6, small {\n    font-family: 'Open Sans', sans-serif;\n    font-weight: 400;\n}\n\np {\n    font-family: 'Raleway', sans-serif;\n    font-weight: 400;\n}\nb {\n    font-weight: 700;\n}\n\n.btn {\n  // Remove transitions from .btn\n  transition: none;\n  &:focus {\n    box-shadow: none;\n  }\n}\n\nbody {\n  background-color: $main-bg-color;\n\n  // Make footer sticky\n  display: flex;\n  min-height: 100vh;\n  flex-direction: column;\n  .flex-content {\n    flex: 1;\n  }\n  // App columns container\n  .apps{\n    margin-top: 40px;\n  }\n}\n\n.pull-right {\n    float: right;\n}\n\n// Pebble color helper class\n.pebble {\n  color: $pebble-color !important;\n  display: inline;\n}\n\n// Pebble colored badge\n.badge-pebble {\n    background-color: $pebble-color;\n    color: #fff;\n}\n\n// Pebble color outline button\n.btn-outline-pebble {\n    margin-left: 10px;\n    color: $pebble-color;\n    border-color: $pebble-color;\n    background-image: none;\n    background-color: transparent;\n\n    // Pebble button button hover styles\n    &:hover, &.active, &:hover:focus {\n        background-color: $pebble-color;\n        color: #333;\n    }\n\n    &:focus {\n      // Override some bootstrap styles\n      color: $pebble-color;\n    }\n}\n\n  // Add new card style called subsection with inverse color (reusable component)\n  .card.subsection {\n    max-width: 720px;\n    margin-left: auto;\n    margin-right: auto;\n    box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12);\n    border-radius: 0;\n    margin-bottom: 40px;\n    display: block;\n  }\n  .card.subsection-inverse {\n    @extend .card.subsection;\n    background-color: #333;\n    border-color: #333;\n }\n .card.subsection-extra {\n   @extend .card.subsection;\n   margin-top: -40px;\n   background-color: #ccc;\n   border-color: #ccc;\n }\n\n// Modify default pagination styles, mostly color (not inside section because it may get reused)\nul.pagination {\n  display: inline-flex;\n  margin-bottom: 0;\n    .page-item {\n        &.active .page-link {\n            // Button is active\n            background: $pebble-color;\n            border-color: $pebble-color;\n            color: #fff;\n        }\n        &.disabled .page-link {\n            // Button is disabled\n            color: #818a91;\n        }\n         .page-link {\n            // Change text colot\n            color: $pebble-color;\n            cursor: pointer;\n            &:hover, &:focus  {\n                // Overwrite hover and focus states\n                text-decoration: none;\n            }\n        }\n    }\n}\n\nheader.main {\n  padding-top: 58px;\n  background: linear-gradient(to bottom, rgba(55, 58, 60, 1) 0%, rgba(55, 58, 60, 1) 65%, rgba(55, 58, 60, 0) 65%, rgba(55, 58, 60, 0) 100%);\n  .title-card {\n    width: 90vw;\n    padding: 20px;\n    background-color: #fff;\n    max-width: 720px;\n    max-height: 320px;\n    margin-left: auto;\n    margin-right: auto;\n    box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12);\n    h3 {\n      margin: 0;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/assets/browserconfig.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<browserconfig>\n  <msapplication>\n    <tile>\n      <square150x150logo src=\"/assets/mstile-150x150.png\"/>\n      <TileColor>#373a3c</TileColor>\n    </tile>\n  </msapplication>\n</browserconfig>\n"
  },
  {
    "path": "src/assets/manifest.json",
    "content": "{\n\t\"name\": \"Rebble Store\",\n\t\"icons\": [\n\t\t{\n\t\t\t\"src\": \"\\/assets\\/android-chrome-192x192.png\",\n\t\t\t\"sizes\": \"192x192\",\n\t\t\t\"type\": \"image\\/png\"\n\t\t},\n\t\t{\n\t\t\t\"src\": \"\\/assets\\/android-chrome-384x384.png\",\n\t\t\t\"sizes\": \"384x384\",\n\t\t\t\"type\": \"image\\/png\"\n\t\t}\n\t],\n\t\"theme_color\": \"#ffffff\",\n\t\"display\": \"standalone\"\n}\n"
  },
  {
    "path": "src/components/Home.vue",
    "content": "<template>\n  <div>\n    <slider v-bind:banners=\"page.banners\"></slider>\n    <main class=\"home apps container\">\n      <tag-list v-if=\"type != 'faces' && page.categories !== undefined\" v-bind:tags=\"page.categories\"></tag-list>\n      <card-collection v-for=\"(collection, index) in page.collections\"  v-bind:key=\"index\" v-bind:elTitle=\"collection.name\" v-bind:cards=\"collection.data\" v-bind:allUrl=\"`/${type}/${collection.slug}`\"></card-collection>\n    </main>\n  </div>\n</template>\n\n<script>\nimport CardCollection from './pages/widgets/CardCollection'\nimport TagList from './pages/widgets/TagList'\nimport Slider from './pages/widgets/HomeSlider'\n\nconst MAX_HOME_COLLECTION_SIZE = 6\n\n// mostRecentCards and freshPicksCards are placeholders. Todo: actually fetch those from the server.\nexport default {\n  name: 'Home',\n  components: {\n    CardCollection,\n    Slider,\n    TagList\n  },\n  data: function () {\n    return {\n      page: {},\n      type: ''\n    }\n  },\n  watch: {\n    '$route' (to, from) {\n      if (to.fullPath !== from.fullPath) {\n        this.page = {}\n        this.get_data(to.params)\n      }\n    }\n  },\n  methods: {\n    get_data: function (routeParams) {\n      var that = this\n      this.type = routeParams.type\n      this.setTitle(this.type === 'faces' || this.type === 'watchfaces' ? 'Watchfaces' : 'Apps')\n      this.$http.get(this.buildResourceUrl(`home/${this.type}`)).then(response => {\n        that.page = response.body\n        this.build_collections()\n      }, response => {\n        console.error(response)\n      })\n    },\n    build_collections: function () {\n      for (let collection of this.page.collections) {\n        collection.data = []\n        for (let id of collection.application_ids) {\n          if (collection.data.length >= MAX_HOME_COLLECTION_SIZE) {\n            // Limit collection size\n            break\n          }\n          collection.data.push(this.page.applications.find(function (app) {\n            return id === app.id\n          }))\n        }\n      }\n    }\n  },\n  beforeMount: function () {\n    this.get_data(this.$route.params)\n  }\n}\n</script>\n\n<style lang=\"scss\">\nmain.home {\n  @media screen and (min-width: 992px) {\n    section .card-columns {\n        column-count: 3 !important;\n    }\n  }\n}\n\n</style>\n"
  },
  {
    "path": "src/components/Navbar.vue",
    "content": "<template>\n    <b-navbar toggleable=\"true\" type=\"dark\" variant=\"dark\" class=\"translucent\" fixed=\"top\">\n      <div class=\"navbar__items left\" v-show=\"showBackButton\">\n        <b-nav-item class=\"back\" v-on:click=\"goBack()\">\n            &#10094;\n        </b-nav-item>\n      </div>\n      <b-navbar-brand to=\"/\">Rebble Store\n          <small>\n            for&nbsp;\n            <div class=\"pebble\">pebble</div>\n          </small>\n      </b-navbar-brand>\n      <div class=\"navbar__items right\">\n        <b-navbar-toggle target=\"categorySelector\"></b-navbar-toggle>\n        <b-nav-item class=\"search\" to=\"/faces/search\">\n          <svg class=\"icon-search\" width=\"25px\" height=\"25px\" viewBox=\"0 0 25 25\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n            <use xlink:href=\"#iconSearch\"></use>\n          </svg>\n        </b-nav-item>\n        <b-nav-item v-if=\"$store.state.userParameters.devMode\" class=\"settings\" to=\"/settings\">\n          <svg class=\"icon-settings\" width=\"25px\" height=\"25px\" viewBox=\"0 0 25 25\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n            <use xlink:href=\"#iconSettings\"></use>\n          </svg>\n        </b-nav-item>\n      </div>\n      <b-collapse id=\"categorySelector\" class=\"text-center\" v-model=\"showCollapse\" is-nav>\n        <div class=\"text-muted category-container\">\n          <div class=\"btn-group btn-group-lg\" role=\"group\">\n            <router-link to=\"/\" v-bind:class=\"{ active: currentRoute == '/faces'}\" class=\"btn btn-outline-secondary btn-watchface\" role=\"button\">\n              <svg class=\"icon-watchface\" width=\"25px\" height=\"25px\" viewBox=\"0 0 25 25\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n                <use xlink:href=\"#iconWatchface\"></use>\n              </svg>\n              Watchfaces\n            </router-link>\n\n            <router-link to=\"/apps\" v-bind:class=\"{ active: currentRoute == '/apps'}\" class=\"btn btn-outline-secondary btn-app\" role=\"button\">\n              <svg class=\"icon-app\" width=\"25px\" height=\"25px\" viewBox=\"0 0 25 25\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n                <use xlink:href=\"#iconApp\"></use>\n              </svg>\n              Apps\n            </router-link>\n          </div>\n        </div>\n      </b-collapse>\n    </b-navbar>\n</template>\n\n<script>\nexport default {\n  name: 'navbar',\n\n  data () {\n    return {\n      showBackButton: false,\n      currentRoute: '/',\n      goBack () {\n        this.$router.go(-1)\n      },\n      showCollapse: false\n    }\n  },\n\n  mounted () {\n    // Update the route state on page load, in case we didn't start at home\n    this.currentRoute = (this.$route.path === '' ? '/' : this.$route.path)\n    this.updateBackButton()\n  },\n\n  watch: {\n    '$route' (to, from) {\n      // Ternary operation makes sure the path is never an empty string when we go home\n      // This is for the class binding on the watchfaces button,\n      // since that is currently the home route\n      this.currentRoute = (this.$route.path === '' ? '/' : this.$route.path)\n      this.updateBackButton()\n      this.showCollapse = false\n    }\n  },\n\n  methods: {\n    updateBackButton () {\n      this.showBackButton = (this.currentRoute !== '/faces' && this.currentRoute !== '/apps')\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n// Select the dark translucent navbar\n.navbar-dark.bg-dark.translucent {\n  // Fix navbar being smaller than 58px\n  min-height: 42px;\n  width: 100%;\n\n    // Navbar brand (app logo or title) styles\n    .navbar-brand {\n        display: initial;\n        margin: 0;\n        position: relative;\n        top: 2px;\n        @media screen and (min-width: 768px) {\n          margin-right: auto;\n          margin-left: auto;\n        }\n\n        // Make text smaller on small screens\n        @media screen and (max-width: 430px) {\n            margin-left: -10px;\n            top: 2px;\n            font-size: 1rem;\n        }\n    }\n\n    li.nav-item {\n      a {\n        padding: 0 2px;\n      }\n    }\n\n    // Navbar items right container\n    .navbar__items {\n\n        // Items in the right\n        &.right {\n          @media screen and (max-width: 767px) {\n            margin-left: auto;\n          }\n\n          // Search Icon\n          .search a {\n            padding-top: 2px; // Move it down\n          }\n\n          // Hamburger icon\n          .navbar-toggler {\n            cursor: pointer;\n            padding: .25rem .35rem;\n            margin-right: .25rem;\n          }\n        }\n\n        // Items in the left\n        &.left {\n          left: 16px;\n\n          // Prevent brand from going on top of this\n          @media screen and (max-width: map-get($grid-breakpoints, sm)) {\n            position: static;\n          }\n\n          // Back arrow button\n          li.back a {\n            position: relative;\n            top: 2px;\n            font-size: 27px;\n            color: rgba(255, 255, 255, 0.5);\n            background: none;\n            border: none;\n            cursor: pointer;\n            // Add margin on breakpoint to prevent brand from being really close\n            margin-right: 20px;\n\n          }\n\n        }\n\n        // Hamburger icon\n        .navbar-toggler, .navbar-toggler:focus {\n            border: 0;\n            border-radius: 0;\n            outline: none;\n        }\n\n        > * {\n            display: inline-block;\n            vertical-align: middle;\n        }\n    }\n\n    @supports (not (backdrop-filter: blur(10px))) and (not (-webkit-backdrop-filter: blur(10px))) {\n      // Only if doesn't supports backdrop filters\n      background-color: rgb(55, 58, 60) !important;\n\n    }\n\n    @supports (backdrop-filter: blur(10px)) or (-webkit-backdrop-filter: blur(10px)) {\n        // Styles that are in here will only apply if backdrop filters are supported by the browser\n         background-color: rgba(55, 58, 60, 0.96)!important;\n\n         // Make the navbar background blurry\n         backdrop-filter: blur(10px);\n    }\n}\n\n// Collapsable menu (this may change in the future) in which you select to browse watchfaces or apps\n#categorySelector {\n    width: 100vw;\n    margin-left: -16px;\n    margin-right: -16px;\n    .category-container {\n      padding: 0.25rem;\n      padding-top: calc(0.25rem + 10px);\n    }\n\n    // Change default button group styles to ones that match the style\n    .btn-group {\n        .btn-outline-secondary {\n            border-color: rgba(204, 204, 204, 0.50);\n            color: rgba(204, 204, 204, 0.50);\n            &.active, &:active,  &:hover {\n                color: #fff;\n                background-color: rgba(204, 204, 204, 0.50);\n\n                // Overwrite bootstrap style\n                border-color: rgba(204, 204, 204, 0.50);\n            }\n            &:focus, &.focus {\n                // Overwrite bootstrap style\n                border-color: rgba(204, 204, 204, 0.50);\n            }\n        }\n\n    }\n}\n</style>\n"
  },
  {
    "path": "src/components/PageFooter.vue",
    "content": "<template>\n  <footer v-bind:class=\"brand ? 'inApp' : 'main'\">\n    <div class=\"brand\" to=\"/\">\n        Rebble Store&nbsp;<small>for&nbsp;pebble</small>\n    </div>\n    <div class=\"main\">\n        <p>© {{ new Date().getFullYear() }} Rebble · <a v-on:click=\"openExternal($store.state.config.contactLink)\">Contact Us</a> · <a  v-on:click=\"openExternal($store.state.config.tosLink)\">Terms</a></p>\n        <a class=\"pull-right\" v-on:click=\"openExternal('$store.state.config.devPortalLink')\">Developer Portal</a>\n    </div>\n  </footer>\n</template>\n\n<script>\nexport default {\n  name: 'page-footer',\n  props: {\n    brand: {\n      type: Boolean,\n      default: false\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n// _footer.scss\n// Footer styles\n\nfooter.main {\n    .main {\n        display: block;\n    }\n    .brand {\n        display: none;\n    }\n    margin-top: 40px;\n    background-color: $pebble-color;\n    color: #fff;\n    padding: 5px 10px;\n    // Footer text styles\n    p {\n        display: inline;\n    }\n\n    // Prevent bootstrap default link color and text-decoration\n    a, a:hover, a:focus {\n        color: #ebebeb;\n        text-decoration: none;\n    }\n}\n\nfooter.inApp {\n    user-select: none;\n    text-align: center;\n    .main {\n        display: none;\n    }\n    .brand {\n        display: block;\n        margin-left: auto;\n        margin-right: auto;\n        font-weight: 700;\n        font-size: 20px;\n        color: rgb(148, 148, 148);\n        small {\n            font-weight: 700;\n        }\n        margin: 20px 10px;\n    }\n}\n\n</style>\n"
  },
  {
    "path": "src/components/SvgContainer.vue",
    "content": "<template>\n  <div id=\"svgContainer\">\n    <!-- <svg id=\"petRock\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n      <defs>\n        <polygon id=\"a\" points=\"160.865 162.763 243.499 144.619 257.327 109.929 242.053 72.586 209.66 56.935 180.398 34.881 138.125 29.331 89.961 41.943 58.829 43.073 31.846 60.054 13.107 99.878 36.862 138.39 100.393 158.597\"/>\n        <mask id=\"d\" width=\"244.22\" height=\"133.432\" x=\"0\" y=\"0\" fill=\"white\">\n          <use xlink:href=\"#a\"/>\n        </mask>\n        <circle id=\"b\" cx=\"80.5\" cy=\"114.5\" r=\"9.5\"/>\n        <mask id=\"e\" width=\"19\" height=\"19\" x=\"0\" y=\"0\" fill=\"white\">\n          <use xlink:href=\"#b\"/>\n        </mask>\n        <circle id=\"c\" cx=\"29.5\" cy=\"101.5\" r=\"9.5\"/>\n        <mask id=\"f\" width=\"19\" height=\"19\" x=\"0\" y=\"0\" fill=\"white\">\n          <use xlink:href=\"#c\"/>\n        </mask>\n      </defs>\n      <g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(-14 3)\">\n        <g transform=\"matrix(-1 0 0 1 271 30)\">\n          <use fill=\"#D8D8D8\" stroke=\"#373A3C\" stroke-width=\"10\" mask=\"url(#d)\" transform=\"rotate(-15 135.217 96.047)\" xlink:href=\"#a\"/>\n          <polyline stroke=\"#373A3C\" stroke-width=\"2\" points=\"21 129.5 60.5 150 118.5 146.5 175 137 236.5 103 253.5 75.5\"/>\n          <circle cx=\"31.5\" cy=\"98.5\" r=\"13.5\" fill=\"#FFFFFF\" stroke=\"#373A3C\" stroke-width=\"2\"/>\n          <circle cx=\"83.5\" cy=\"110.5\" r=\"13.5\" fill=\"#FFFFFF\" stroke=\"#373A3C\" stroke-width=\"2\"/>\n          <use fill=\"#373A3C\" stroke=\"#373A3C\" stroke-width=\"2\" mask=\"url(#e)\" xlink:href=\"#b\"/>\n          <use fill=\"#373A3C\" stroke=\"#373A3C\" stroke-width=\"2\" mask=\"url(#f)\" xlink:href=\"#c\"/>\n          <circle cx=\"86\" cy=\"113\" r=\"2\" fill=\"#FFFFFF\"/>\n          <circle cx=\"34\" cy=\"100\" r=\"2\" fill=\"#FFFFFF\"/>\n        </g>\n        <polygon fill=\"#FFFFFF\" stroke=\"#373A3C\" stroke-width=\"5\" points=\"296.576 0 391.886 0 417 22.464 417 91.623 391.886 111.304 308.5 111.304 282.089 128 282.089 100.5 271 91.623 271 22.464\"/>\n        <text fill=\"#000000\" font-family=\"OpenSans-Light, Open Sans\" font-size=\"44\" font-weight=\"300\">\n          <tspan x=\"307\" y=\"71\">404</tspan>\n        </text>\n      </g>\n    </svg> -->\n</div>\n</template>\n\n<script>\nconst iconList = require.context('@/assets/svg/', true, /\\.svg$/)\niconList.keys().forEach(key => iconList(key))\n\nexport default {\n  name: 'svg-container'\n}\n</script>\n\n<style lang=\"scss\">\n// Remember that font-awesome is included in the projects\n\n  #svgContainer {\n    display: none;\n  }\n\n  #svgContainer * {\n    stroke: inherit;\n    fill: inherit;\n  }\n\n  .icon-search {\n    fill: transparent;\n    stroke: #9b9d9e;\n    // This should be the color but the overlapping paths make it not ideal because it is 50% translucent\n    // stroke: rgba(255, 255, 255, 0.5);\n  }\n\n  .icon-settings {\n      fill: transparent;\n      stroke: #9b9d9e;\n  }\n\n  .btn-watchface, .btn-app {\n    svg.icon-watchface, svg.icon-app {\n        fill: transparent;\n        stroke: #828682;\n        use {\n            fill: transparent;\n            color: #828682;\n        }\n    }\n  }\n\n  .icon-inverted-thumbs-up {\n    fill: #FFF;\n    stroke: #889097;\n  }\n\n// Select the dark translucent navbar\n.navbar-dark.bg-dark.translucent {\n    // Search, magnifier icon\n    a.search {\n        svg {\n            height: 22px;\n            margin-top: 3px;\n        }\n    }\n    #categorySelector {\n\n        // Watchface/App Selecctor icon styles\n        .btn-group {\n            .btn-outline-secondary {\n                &.active, &:active,  &:hover {\n                    //Hover and active styles\n                    svg.icon-watchface, svg.icon-app  {\n                        fill: transparent;\n                        stroke: #FFF;\n                        // Watchface Icon\n                        use {\n                            color: #fff;\n                        }\n                    }\n                }\n            }\n\n        }\n    }\n}\n\n// App columns container\n.apps {\n    .card-columns {\n        // Modify bootstrap's default .card-columns stylse\n\n        // This isn't inside the \"a\" to avoid issue if it doesn't ends up being inside a \"a\"\n        .card {\n            // Make it smaller on small screens\n            @media screen and (max-width: map-get($grid-breakpoints, sm)) {\n                .card-text {\n                    svg.thumbs-up {\n                        height: 14px;\n                        top: 3px;\n                    }\n                }\n            }\n            .card-text {\n                // Style thumbs up svg inside of app card\n                svg.thumbs-up {\n                    height: 16px;\n                    margin-right: -6px;\n                }\n            }\n        }\n    }\n}\n\n// Change the font-size of the arrow in the buttons that are located at the bottom of the app-details container\n.app-details{\n    a.app-button {\n        div {\n            i {\n                font-size: 25px\n            }\n        }\n    }\n}\n\n// Set styles of SVG in the app-button-container (app-details and app-versions page)\n.app-button-container{\n    .btn {\n        @media screen and (max-width: 430px) {\n            // Set SVG size and margin when screen smaller than 430px to avoid breaking all styles\n            svg {\n                height: 16px !important;\n                margin-right: -5px;\n            }\n        }\n        // Set SVG size and margin\n        svg {\n            height: 20px;\n            position: relative;\n        }\n\n        // Set thumbs up SVG styles\n        &.btn-thumbs-up {\n            // Default styles are set in the svg\n\n            // Thumbs up svg ivon hover styles\n            .icon-thumbs-up {\n                fill: #333;\n                stroke: #ccc;\n            }\n            /*\n            We're triggering the hover on the parent button instead of the SVG.\n            */\n            &.active {\n                svg {\n                    fill: #ccc;\n                    stroke: #333;\n                }\n            }\n\n            &.disabled {\n                // Don't change any style if disabled\n                &:hover, &.active {\n                    svg {\n                        fill: #333;\n                        stroke: #ccc;\n                    }\n                }\n            }\n        }\n\n        // Download button styles\n        &.btn-download {\n            // Default styles are set in the svg\n\n            // Download svg icon hover styles\n            .icon-download {\n                fill: #333;\n                stroke: #ff4700;\n            }\n            &:hover, &.active {\n                svg {\n                    fill: #ff4700;\n                    stroke: #333;\n                }\n            }\n        }\n    }\n}\n\n</style>\n"
  },
  {
    "path": "src/components/pages/AppDetails.vue",
    "content": "<template>\n  <main v-if=\"Object.entries(app).length !== 0\" class=\"text-center\">\n    <screenshot-list v-bind:screenshots=\"app.screenshot_images\"></screenshot-list>\n\n    <div class=\"card subsection text-left p-3 app-details\">\n      <h1>Description</h1> <hr>\n      <pre class=\"description\">{{ app.description }}</pre>\n      <table class=\"extra-table\">\n        <tr>\n          <td>Developer</td>\n          <td>{{ app.author }}</td>\n        </tr>\n        <tr>\n          <td>Category</td>\n          <td>\n            <router-link v-if=\"app.type !== 'watchapp'\" v-bind:to=\"'/collection/' + app.category_id\"><span class=\"badge badge-pill badge-pebble\">{{ app.category }}</span></router-link>\n            <span v-if=\"app.type === 'watchapp'\" class=\"badge badge-pill badge-pebble\">{{ app.category }}</span>\n          </td>\n        </tr>\n        <tr v-if=\"app.latest_release\">\n          <td>Updated</td>\n          <td>{{ app.latest_release.published_date | formatDate }}</td>\n        </tr>\n        <tr v-if=\"app.latest_release\">\n          <td>Version</td>\n          <td>{{ app.latest_release.version }}</td>\n        </tr>\n      </table>\n      <router-link v-if=\"app.changelog.length > 0\" v-bind:to=\"'/app/' + $route.params.id + '/versions/'\" class=\"app-button\">\n        <div>\n          Version Information <i class=\"fa fa-angle-right float-right\" aria-hidden=\"true\"></i>\n        </div>\n      </router-link>\n      <a v-if=\"app.website != null\" v-on:click=\"openExternal(app.website)\" class=\"app-button\">\n        <div>\n          Website Link <i class=\"fa fa-angle-right float-right\" aria-hidden=\"true\"></i>\n        </div>\n      </a>\n      <!--a v-if=\"app.appInfo.supportUrl != ''\" v-bind:href=\"app.appInfo.supportUrl\" class=\"app-button\">\n        <div>Support <i class=\"fa fa-angle-right float-right\" aria-hidden=\"true\"></i>\n        </div>\n      </a-->\n      <a v-if=\"app.source != null\" v-on:click=\"openExternal(app.source)\" class=\"app-button\" >\n        <div>Source code <i class=\"fa fa-angle-right float-right\" aria-hidden=\"true\"></i></div>\n      </a>\n      <router-link v-bind:to=\"'/author/' + app.developer_id\" class=\"app-button\">\n        <div>More From This Developer<i class=\"fa fa-angle-right float-right\" aria-hidden=\"true\"></i>\n        </div>\n      </router-link>\n      <a v-if=\"app.latest_release && app.latest_release.pbw_file != '' && $store.state.userParameters.devMode\" v-on:click=\"openExternal(app.latest_release.pbw_file)\" class=\"app-button\">\n        <div>Download .pbw<i class=\"fa fa-angle-right float-right\" aria-hidden=\"true\"></i>\n        </div>\n      </a>\n    </div>\n  </main>\n</template>\n\n<script>\nimport ScreenshotList from './widgets/ScreenshotList'\n\nexport default {\n  name: 'app-details',\n  components: {\n    ScreenshotList\n  },\n  props: {\n    app: {\n      default: null\n    },\n    clientWatchPlatform: {\n      default: null\n    }\n  },\n  watch: {\n    'app' (to, from) {\n      this.setTitle(this.app.title)\n    }\n  },\n  beforeMount: function () {\n    if (this.app.title !== undefined) {\n      this.setTitle(this.app.title)\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n// App details container (below screenshots)\n.app-details {\n  margin-bottom: 0;\n}\n</style>\n"
  },
  {
    "path": "src/components/pages/AppVersions.vue",
    "content": "<template>\n    <main class=\"text-center\">\n      <div class=\"card subsection text-left p-3 app-details\" v-for=\"(changelog, index) in app.changelog\" v-bind:key=\"index\">\n        <h2>Version {{ changelog.version }}</h2> <h3 class=\"float-right\">{{ changelog.published_date | formatDate }}</h3><hr>\n        <pre class=\"description\">{{ changelog.release_notes }}</pre>\n        </div>\n    </main>\n</template>\n\n<script>\nimport AppTitleBar from './widgets/AppTitleBar'\n\nexport default {\n  name: 'app-version',\n  components: {\n    AppTitleBar\n  },\n  props: {\n    app: {}\n  },\n  data: function () {\n    return {\n      versions: {\n        'versions': []\n      }\n    }\n  },\n  beforeMount: function () {\n    if (this.app.title !== undefined) {\n      this.setTitle(`${this.app.title} Versions`)\n    }\n  },\n  watch: {\n    'app' (to, from) {\n      this.setTitle(`${this.app.title} Versions`)\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n// App details container (below screenshots)\n.app-details {\n    &:last-of-type {\n        margin-bottom: 0;\n    }\n}\n</style>\n"
  },
  {
    "path": "src/components/pages/AppView.vue",
    "content": "<template>\n  <section v-bind:class=\"app.type\" >\n    <header class=\"main\" v-bind:class=\"($store.state.userParameters.inApp && !app.header_images) ? 'inApp no-banner': ''\">\n      <slider v-if=\"app.header_images != ''\" v-bind:banners=\"app.header_images\"></slider>\n    </header>\n    <app-title-bar v-bind:app=\"app\" v-bind:class=\"($store.state.userParameters.inApp && !app.header_images) ? 'title-bar extra-margin': ''\"></app-title-bar>\n\n    <router-view v-bind:app=\"app\" ></router-view>\n  </section>\n</template>\n\n<script>\nimport AppTitleBar from './widgets/AppTitleBar'\nimport Slider from './widgets/AppSlider'\nimport ScreenshotList from './widgets/ScreenshotList'\nimport { Native } from '../../services'\n\nexport default {\n  name: 'app-view',\n  components: {\n    AppTitleBar,\n    ScreenshotList,\n    Slider\n  },\n  data: function () {\n    return {\n      app: {}\n    }\n  },\n  methods: {\n    get_app: function (id) {\n      this.$http.get(this.buildResourceUrl(`apps/id/${id}`)).then(response => {\n        this.app = response.body.data[0]\n        if (this.$store.state.userParameters.inApp) {\n          Native.send('setVisibleApp', this.app)\n        }\n      }, response => {\n        console.error(response)\n      })\n    }\n  },\n  beforeMount: function () {\n    this.get_app(this.$route.params.id)\n  }\n}\n</script>\n\n<style lang=\"scss\">\n// _app-details.scss\n// App details page styles\n\n// Similar to carousel but only used when displaying only one image\nheader.main {\n  &.inApp.no-banner {\n    padding: 0;\n    display: none;\n    min-height: 0;\n  }\n  min-height: 90px;\n  .app-banner {\n    margin-bottom: -15px;\n    max-width: 720px;\n    max-height: 320px;\n    margin-left: auto;\n    margin-right: auto;\n\n    img {\n        width: 100%;\n        max-width: 720px;\n    }\n  }\n}\n\n.title-bar.extra-margin {\n  margin-top: 43px;\n}\n\n// App details container (below screenshots)\n.app-details {\n  margin-bottom: 0;\n  // Main title\n  h1 {\n    font-size: 20px;\n    margin: none;\n  }\n  // Separator\n  hr {\n    margin-top: 4px;\n  }\n\n  // H2 and H3 used in app-versions\n  h2 {\n    font-size: 16px;\n    display: inline;\n  }\n  h3 {\n    display: inline;\n    font-size: 14px;\n    margin-bottom: 0;\n    margin-top: 4px;\n  }\n\n  // App Description container\n  pre.description {\n    // Make sentences break and prevent scrollbars\n    word-wrap: break-word;\n    white-space: pre-wrap;\n\n    // Change app description font and weight\n    font-family: 'Raleway', sans-serif;\n    font-weight: 400;\n  }\n\n  // App extra-info table (Author, Version, Release date, etc...)\n  table {\n    margin-top: 30px;\n    margin-bottom: 30px;\n    tr td {\n      padding-right: 10px;\n      font-family: 'Open Sans', sans-serif;\n    }\n    tr td:last-child {\n      padding-right: 0;\n      // A second font to make it look different from the first column\n      font-family: 'Raleway', sans-serif;\n    }\n  }\n\n  // Buttons at the bottom of the app-details container\n  a.app-button {\n    div {\n      width: calc(100% + 2rem);\n      padding: 1rem;\n      margin-left: -16px;\n      margin-right: -16px;\n      border-top: 1px solid #e1e1e1;\n      // Change the fonts from the buttons at the bottom of the app-details container\n      font-family: 'Open Sans', sans-serif;\n    }\n\n    color: $pebble-color;\n    &:last-child {\n      div {\n        margin-bottom: -1rem;\n      }\n    }\n    &:hover, &:focus {\n      text-decoration: none;\n      outline: none;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/pages/Author.vue",
    "content": "<template>\n  <div>\n    <header class=\"main\">\n      <div v-if=\"page.data !== undefined\" class=\"title-card\">\n        <h3>Apps by: {{ page.data[0].author }}</h3>\n      </div>\n    </header>\n    <main class=\"apps container text-center\">\n      <card-collection :showTop=\"false\" v-bind:cards=\"page.data\"></card-collection>\n\n      <pagination v-bind:links=\"page.links\" v-bind:baseUrl=\"`/author/${this.$route.params.id}`\"></pagination>\n    </main>\n  </div>\n</template>\n\n<script>\nimport CardCollection from './widgets/CardCollection'\nimport Pagination from './widgets/Pagination'\n\nexport default {\n  name: 'author',\n  components: {\n    CardCollection,\n    Pagination\n  },\n  data: function () {\n    return {\n      page: {},\n      pageLimit: 6\n    }\n  },\n  watch: {\n    '$route' (to, from) {\n      this.get_data()\n    }\n  },\n  methods: {\n    get_author: function (id, offsetPage) {\n      var offset = this.pageLimit * (offsetPage - 1)\n      this.$http.get(`${this.buildResourceUrl(`apps/dev/${id}`)}&offset=${offset}&limit=${this.pageLimit}`).then(response => {\n        this.page = response.body\n        this.setTitle(this.page.data[0].author)\n      }, response => {\n        console.error(response)\n      })\n    },\n    get_data: function () {\n      this.get_author(this.$route.params.id, this.$route.params.page)\n    }\n  },\n  beforeMount: function () {\n    this.get_data()\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n</style>\n"
  },
  {
    "path": "src/components/pages/Category.vue",
    "content": "<template>\n  <div>\n    <header class=\"main\">\n        <div class=\"title-card\">\n            <h3>{{id | readable-name}}</h3>\n        </div>\n    </header>\n    <main class=\"apps container text-center\">\n        <div class=\"text-center header-tool\">\n          <div class=\"btn-group btn-group-sm\" role=\"group\">\n            <router-link v-bind:to=\"'/category/' + id + '/hearts/1'\" v-bind:class=\"sort == 'hearts' ? 'btn btn-outline-secondary active': 'btn btn-outline-secondary'\" role=\"button\">Most Liked</router-link>\n            <router-link v-bind:to=\"'/category/' + id + '/updated/1'\" v-bind:class=\"sort == 'updated' ? 'btn btn-outline-secondary active': 'btn btn-outline-secondary'\" role=\"button\">Recently Added</router-link>\n          </div>\n        </div>\n\n        <card-collection :showTop=\"false\" v-bind:cards=\"page.data\"></card-collection>\n        <pagination v-bind:links=\"page.links\" v-bind:baseUrl=\"`/category/${this.id}/${this.sort}`\"></pagination>\n    </main>\n    </div>\n</template>\n\n<script>\nimport CardCollection from './widgets/CardCollection'\nimport Pagination from './widgets/Pagination'\n\nexport default {\n  name: 'category',\n  components: {\n    CardCollection,\n    Pagination\n  },\n  data: function () {\n    return {\n      page: {},\n      id: '',\n      sort: '',\n      offsetPage: 1,\n      pageLimit: 24\n    }\n  },\n  watch: {\n    '$route' (to, from) {\n      this.get_data(to.params)\n    }\n  },\n  methods: {\n    get_category: function () {\n      var that = this\n      var offset = this.pageLimit * (this.offsetPage - 1)\n      this.$http.get(`${this.buildResourceUrl(`apps/category/${this.id}`)}&sort=${this.sort}&offset=${offset}&limit=${this.pageLimit}`).then(response => {\n        that.page = response.body\n      }, response => {\n        console.error(response)\n      })\n    },\n    get_data: function (routeParams) {\n      this.id = routeParams.id\n      this.sort = routeParams.sort\n      this.offsetPage = routeParams.page\n\n      this.setTitle(this.$options.filters['readable-name'](this.id))\n      this.get_category()\n    }\n  },\n  beforeMount: function () {\n    this.get_data(this.$route.params)\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.header-tool {\n  margin-bottom: 40px;\n}\n</style>\n"
  },
  {
    "path": "src/components/pages/Collection.vue",
    "content": "<template>\n  <div>\n    <header class=\"main\">\n        <div class=\"title-card\">\n            <h3>Collection: {{slug | readable-name}}</h3>\n        </div>\n    </header>\n    <main class=\"apps container text-center\">\n\n        <card-collection :showTop=\"false\" v-bind:cards=\"page.data\"></card-collection>\n\n        <pagination v-bind:links=\"page.links\" v-bind:baseUrl=\"`/${this.type}/${this.slug}`\"></pagination>\n    </main>\n    </div>\n</template>\n\n<script>\nimport CardCollection from './widgets/CardCollection'\nimport Pagination from './widgets/Pagination'\n\nexport default {\n  name: 'collection',\n  components: {\n    CardCollection,\n    Pagination\n  },\n  data: function () {\n    return {\n      page: {},\n      slug: '',\n      sort: '',\n      offsetPage: 1,\n      pageLimit: 24,\n      type: ''\n    }\n  },\n  watch: {\n    '$route' (to, from) {\n      this.get_data(to.params)\n    }\n  },\n  methods: {\n    get_collection: function () {\n      var offset = this.pageLimit * (this.offsetPage - 1)\n      this.$http.get(`${this.buildResourceUrl(`apps/collection/${this.slug}/${this.type}`)}&offset=${offset}&limit=${this.pageLimit}`).then(response => {\n        this.page = response.body\n      }, response => {\n        console.error(response)\n      })\n    },\n    get_data: function (routeParams) {\n      this.slug = routeParams.slug\n      this.offsetPage = routeParams.page\n      this.type = routeParams.type\n\n      this.setTitle(this.$options.filters['readable-name'](this.slug))\n      this.get_collection()\n    }\n  },\n  beforeMount: function () {\n    this.get_data(this.$route.params)\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n</style>\n"
  },
  {
    "path": "src/components/pages/Error.vue",
    "content": "<template>\n  <div>\n    <main class=\"container text-xs-center\">\n      <section>\n        <div class=\"page-error page-error--404\">\n\n          <h2>Our pet rock has lost your page, sorry about that</h2>\n\n          <svg class=\"pet-rock-pebble\" width=\"406\" height=\"201\" viewBox=\"0 0 406 201\">\n              <g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(2 2)\">\n                <polygon fill=\"#D8D8D8\" stroke=\"#373A3C\" stroke-width=\"4\" points=\"131.999 196.638 216.6 196.071 237.338 165.014 230.162 125.311 201.731 103.267 177.694 75.611 137.498 61.394 87.765 63.716 57.078 58.349 27.154 69.348 .545 104.406 15.774 147.016 73.715 179.99\"/>\n                <polyline stroke=\"#373A3C\" stroke-width=\"2\" points=\"237.049 166.139 208.87 183.73 134.959 181.099 79.012 166.139 27.964 139.016 2.519 105.431\"/>\n                <g transform=\"translate(159 127)\">\n                  <circle cx=\"13.5\" cy=\"13.5\" r=\"13.5\" fill=\"#FFFFFF\" stroke=\"#373A3C\" stroke-width=\"2\" transform=\"matrix(-1 0 0 1 27 0)\"/>\n                  <circle cx=\"16.5\" cy=\"17.5\" r=\"9.5\" fill=\"#373A3C\" stroke=\"#373A3C\" stroke-width=\"2\" transform=\"matrix(-1 0 0 1 33 0)\"/>\n                  <circle cx=\"11\" cy=\"16\" r=\"2\" fill=\"#FFFFFF\" transform=\"matrix(-1 0 0 1 22 0)\"/>\n                </g>\n                <g transform=\"translate(211 115)\">\n                  <circle cx=\"13.5\" cy=\"13.5\" r=\"13.5\" fill=\"#FFFFFF\" stroke=\"#373A3C\" stroke-width=\"2\" transform=\"matrix(-1 0 0 1 27 0)\"/>\n                  <circle cx=\"15.5\" cy=\"16.5\" r=\"9.5\" fill=\"#373A3C\" stroke=\"#373A3C\" stroke-width=\"2\" transform=\"matrix(-1 0 0 1 31 0)\"/>\n                  <circle cx=\"11\" cy=\"15\" r=\"2\" fill=\"#FFFFFF\" transform=\"matrix(-1 0 0 1 22 0)\"/>\n                </g>\n                <g transform=\"translate(256)\">\n                  <polygon fill=\"#FFFFFF\" stroke=\"#373A3C\" stroke-width=\"4\" points=\"25.576 0 120.886 0 146 22.464 146 91.623 120.886 111.304 37.5 111.304 11.089 128 11.089 100.5 0 91.623 0 22.464\"/>\n                  <text fill=\"#000000\" transform=\"translate(36 24)\" font-size=\"44\" font-family=\"OpenSans-Light, Open Sans\" font-weight=\"300\">\n                    <tspan x=\"0\" y=\"47\">404</tspan>\n                  </text>\n                </g>\n              </g>\n            </svg>\n\n          <h4>We're getting the following message {{ 404 }}</h4>\n          <div class=\"page-error_buttons\">\n            <button v-on:click=\"reload()\" class=\"btn btn-outline-secondary\">Try again</button>\n            <router-link v-bind:to=\"'/'\" class=\"btn btn-outline-pebble\">Back to home</router-link>\n          </div>\n        </div>\n      </section>\n    </main>\n  </div>\n</template>\n\n<script>\nexport default {\n  name: 'error',\n  data: function () {\n    return {\n      reload () {\n        this.$router.go(0)\n      }\n    }\n  },\n  beforeMount: function () {\n    this.setTitle('404 Page not found')\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n// _error.scss\n// Error page styles\n\n.page-error {\n  // Calculate top margin 58 px ar of the navbar\n  margin-top: 40px + 58px;\n  min-height: 400px;\n  height: 80vh;\n  display: flex;\n  flex-direction: column;\n  max-width: 36rem;\n  margin-left: auto;\n  margin-right: auto;\n  align-items: center;\n  justify-content: center;\n\n  h4 {\n    // Change font to make this page more interesting\n    font-family: 'Raleway', sans-serif;\n    font-weight: 400;\n  }\n\n  .pet-rock-pebble {\n      // Mascot svg\n      margin-top: 3rem;\n      margin-bottom: 3rem;\n    }\n\n  .page-error_buttons {\n    // Minimal style changes to buttons\n    margin-top: 3rem;\n    .btn-outline-pebble {\n      &:hover {\n        color: #f4f3f4;\n      }\n    }\n  }\n}\n\n</style>\n"
  },
  {
    "path": "src/components/pages/Search.vue",
    "content": "<template>\n  <ais-instant-search :search-client=\"rebbleSearch\" index-name=\"rebble-appstore-production\" :routing=\"routing\">\n    <ais-configure :hits-per-page.camel=\"24\" :tag-filters.camel=\"build_filter_list()\" />\n    <div>\n      <header class=\"main\">\n        <div class=\"title-card search\">\n          <ais-search-box>\n            <input autofocus placeholder=\"Search\" type=\"search\" autocorrect=\"off\" autocapitalize=\"off\" autocomplete=\"off\" spellcheck=\"false\" slot-scope=\"{ currentRefinement, refine }\" :value=\"currentRefinement\" @input=\"refine($event.currentTarget.value)\">\n          </ais-search-box>\n        </div>\n      </header>\n      <main class=\"apps container text-center\">\n        <div class=\"text-center header-tool\">\n          <div class=\"btn-group btn-group-sm\" role=\"group\">\n            <router-link v-bind:to=\"'/faces/search'\" v-bind:class=\"type == 'faces' ? 'btn btn-outline-secondary active': 'btn btn-outline-secondary'\" role=\"button\">Watchfaces</router-link>\n            <router-link v-bind:to=\"'/apps/search'\" v-bind:class=\"type == 'apps' ? 'btn btn-outline-secondary active': 'btn btn-outline-secondary'\" role=\"button\">Apps</router-link>\n          </div>\n        </div>\n        <ais-state-results>\n          <template slot-scope=\"{ hits }\">\n            <ais-hits v-if=\"hits.length > 0\">\n              <card-collection  slot-scope=\"{ items }\" :showTop=\"false\" v-bind:cards=\"items\" v-bind:searchData=\"true\"></card-collection>\n            </ais-hits>\n            <nav v-if=\"hits.length > 0\">\n              <ais-pagination :classNames=\"{\n                  'ais-Pagination-list': 'pagination',\n                  'ais-Pagination-item': 'page-item',\n                  'ais-Pagination-link': 'page-link',\n                  'ais-Pagination-item--selected': 'active',\n                  'ais-Pagination-item--disabled': 'disabled'\n\n                  }\" />\n            </nav>\n            <div v-if=\"hits.length <= 0\">\n              No results were found.\n            </div>\n\n          </template>\n        </ais-state-results>\n      </main>\n    </div>\n  </ais-instant-search>\n</template>\n\n<script>\nimport algoliasearch from 'algoliasearch/lite'\nimport { searchRouting } from '../../router/search-router'\nimport { simple as simpleMapping } from 'instantsearch.js/es/lib/stateMappings'\nimport { platformEnum, hardwareEnum } from '../../store/userParameters'\n\nexport default {\n  name: 'search',\n  props: {\n    type: {\n      type: String,\n      default: 'faces'\n    }\n  },\n  data () {\n    return {\n      rebbleSearch: algoliasearch(\n        '7683OW76EQ',\n        '252f4938082b8693a8a9fc0157d1d24f'\n      ),\n      routing: {\n        router: searchRouting.router,\n        stateMapping: simpleMapping()\n      }\n    }\n  },\n  methods: {\n    build_filter_list: function () {\n      var filterList = []\n      if (this.$store.state.userParameters.platform !== platformEnum.all) {\n        filterList.push(this.$store.state.userParameters.platform)\n      }\n      if (this.$store.state.userParameters.hardware !== hardwareEnum.all) {\n        filterList.push(this.$store.state.userParameters.hardware)\n      }\n      if (this.type === 'faces' || this.type === 'watchfaces') {\n        filterList.push('(watchface)')\n      } else if (this.type === 'apps' || this.type === 'watchapps') {\n        filterList.push('(watchapp,companion-app)')\n      }\n      return filterList.join(',')\n    }\n  },\n  beforeMount: function () {\n    this.setTitle('Search')\n  }\n\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.title-card {\n  padding: 0;\n}\n\n.search {\n  height: 70px;\n  input {\n    margin: -20px;\n    font-family: 'Open Sans', sans-serif;\n    font-weight: 400;\n    width: calc(100% + 40px);\n    height: 70px;\n    font-size: 1.75rem;\n    padding: 20px;\n    border: 0;\n    background: none;\n    color: #373a3c;\n    &::-webkit-search-decoration,\n    &::-webkit-search-cancel-button,\n    &::-webkit-search-results-button,\n    &::-webkit-search-results-decoration { display: none; }\n\n    &:focus {\n      outline: none;\n    }\n  }\n}\n.header-tool {\n  margin-bottom: 40px;\n}\n\n.ais-Pagination {\n  margin-top: 40px\n}\n</style>\n"
  },
  {
    "path": "src/components/pages/Settings.vue",
    "content": "<template>\n    <div>\n        <header class=\"main\">\n            <div class=\"title-card\">\n                <h3>Pebble Type</h3>\n            </div>\n        </header>\n        <main class=\"text-center\">\n            <form>\n                <input type=\"radio\" id=\"aplite\" value=\"aplite\" v-model=\"hardware\"> <label for=\"aplite\">Pebble\n                Original/Steel (aplite)</label><br/>\n                <input type=\"radio\" id=\"basalt\" value=\"basalt\" v-model=\"hardware\"> <label for=\"basalt\">Pebble Time and Time Steel\n                (basalt)</label><br/>\n                <input type=\"radio\" id=\"chalk\" value=\"chalk\" v-model=\"hardware\"> <label for=\"chalk\">Pebble Time Round\n                (chalk)</label><br/>\n                <input type=\"radio\" id=\"diorite\" value=\"diorite\" v-model=\"hardware\"> <label for=\"diorite\">Pebble 2\n                (diorite)</label><br/>\n            </form>\n        </main>\n    </div>\n</template>\n\n<script>\nexport default {\n  name: 'settings',\n  data: function () {\n    return {\n      hardware: this.$store.state.userParameters.hardware\n    }\n  },\n  watch: {\n    hardware: function (p) {\n      this.hardware = p\n      this.$store.state.userParameters.hardware = p\n      window.localStorage.setItem('hardware', p)\n    }\n  },\n  beforeMount: function () {\n    this.setTitle('Settings')\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\nmain {\n  margin-top: 40px;\n}\n</style>\n"
  },
  {
    "path": "src/components/pages/widgets/AppSlider.vue",
    "content": "<template>\n  <div v-if=\"banners != null  && banners[0] != null\" id=\"banner-carousel\" class=\"carousel slide\" data-ride=\"carousel\">\n    <ol class=\"carousel-indicators\" v-if=\"banners[1] != null\">\n      <li v-for=\"(banner, index) in banners\" v-bind:key=\"index\" data-target=\"#banner-carousel\" v-bind:data-slide-to=\"index\" v-bind:class=\"index == 0 ? 'active' : ''\"></li>\n    </ol>\n    <div class=\"carousel-inner\" role=\"listbox\">\n      <div v-bind:class=\"index == 0 ? 'carousel-item active' : 'carousel-item'\" v-for=\"(banner, index) in banners\" v-bind:key=\"index\"><single-banner v-bind:bannerSrc=\"banner['720x320']\"></single-banner></div>\n    </div>\n    <a  v-if=\"banners[1] != null\" class=\"carousel-control carousel-control-prev\" href=\"#banner-carousel\" role=\"button\" data-slide=\"prev\">\n      <i class=\"fa fa-angle-left\" aria-hidden=\"true\"></i>\n      <span class=\"sr-only\">Previous</span>\n    </a>\n    <a  v-if=\"banners[1] != null\" class=\"carousel-control carousel-control-next\" href=\"#banner-carousel\" role=\"button\" data-slide=\"next\">\n      <i class=\"fa fa-angle-right\" aria-hidden=\"true\"></i>\n      <span class=\"sr-only\">Next</span>\n    </a>\n  </div>\n</template>\n\n<script>\nimport SingleBanner from './SingleBanner'\n\nexport default {\n  name: 'slider',\n  components: {\n    SingleBanner\n  },\n  props: {\n    banners: null\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n  .carousel {\n    max-width: 720px;\n    max-height: 320px;\n    margin-left: auto;\n    margin-right: auto;\n    background-color: #000;\n    box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12);\n\n    .carousel-indicators {\n      // Slide indicators, make them smaller with browser resize\n      margin-bottom: 0;\n      margin-left: 30%;\n      margin-right: 30%;\n\n    }\n\n    .carousel-control {\n      i {\n        // carousel control < > icons\n        font-size: 30px;\n      }\n    }\n\n    .carousel-item.active {\n      display: block;\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/components/pages/widgets/AppTitleBar.vue",
    "content": "<template>\n<!-- Fix url args -->\n  <div  v-if=\"Object.entries(app).length !== 0\" v-bind:class=\"(this.$store.state.userParameters.inApp) ? 'app-title-bar-cont sticky-top': 'app-title-bar-cont'\">\n      <div class=\"card subsection-inverse card-inverse text-left p-3 app-title-bar\">\n        <img class=\"app-icon\" v-if=\"app.icon_image != null && app.icon_image['48x48'] != ''\" v-bind:src=\"app.icon_image['48x48']\">\n        <div v-bind:class=\"app.icon_image ? 'title-author app' :  'title-author face'\">\n          <h1 class=\"tile\">{{ app.title }}</h1>\n          <h2 class=\"author\">{{ app.author }}</h2>\n        </div>\n\n        <div class=\"app-button-container float-right\">\n          <button type=\"button\" v-bind:class=\"heartClass\" v-on:click=\"toggle_heart_button_state\" ref=\"heartsButton\">\n            <svg class=\"svg-icon icon-thumbs-up\" width=\"25px\" height=\"25px\" viewBox=\"0 0 25 25\">\n              <use xlink:href=\"#iconThumbsUp\"></use>\n            </svg>\n\n            {{ hearts }}\n          </button>\n          <get-app-button v-bind:app=\"app\" v-bind:state=\"added\"  ref=\"addButton\"></get-app-button>\n        </div>\n      </div>\n      <div class=\"card subsection-extra card-inverse text-left p-2\" v-if=\" app.companions != undefined && (app.companions.ios != null || app.companions.android != null) && app.type != 'watchface'\">\n        <h2 v-if=\"app.type === 'companion-app'\">Requires Companion</h2>\n        <h2 v-if=\"app.type !== 'companion-app'\">Companion</h2>\n        <div class=\"ml-auto\">\n          <a v-if=\"app.companions.ios\" v-bind:href=\"app.companions.ios.url\" target=\"_blank\">\n            <svg class=\"app-icon\" width=\"22px\" height=\"22px\">\n              <use xlink:href=\"#iconApple\"></use>\n            </svg>\n          </a>\n          <h2 v-if=\"app.companions.ios && app.companions.android\">\n            +\n          </h2>\n          <a v-if=\"app.companions.android\" v-bind:href=\"app.companions.android.url\" target=\"_blank\">\n            <svg class=\"app-icon\" width=\"22px\" height=\"22px\">\n              <use xlink:href=\"#iconAndroid\"></use>\n            </svg>\n          </a>\n        </div>\n      </div>\n  </div>\n</template>\n\n<script>\nimport GetAppButton from './GetAppButton'\n\nexport default {\n  name: 'ScreenshotList',\n  components: {\n    GetAppButton\n  },\n  props: ['app', 'urlArguments'],\n  data: function () {\n    return {\n      heartClass: 'btn btn-outline-secondary btn-thumbs-up disabled',\n      hearts: 0,\n      hearted: false,\n      added: false,\n      flagged: false\n    }\n  },\n  methods: {\n    get_user_data: function (id) {\n      if (this.$store.state.secure.accessToken !== '' && this.$store.state.secure.accessToken != null) {\n        this.$http.get(this.$store.state.config.devPortalBackendUrl + '/users/me', {headers: {Authorization: 'Bearer ' + this.$store.state.secure.accessToken}}).then(response => {\n          let userInfo = response.body.users[0]\n          this.added = !(!userInfo || !~userInfo.added_ids.indexOf(id))\n          this.hearted = !(!userInfo || !~userInfo.voted_ids.indexOf(id))\n          this.flagged = !(!userInfo || !~userInfo.flagged_ids.indexOf(id))\n          console.log(this.added)\n          this.build_hearts_class()\n        }, response => {\n          console.error(response)\n        })\n      }\n    },\n    change_heart: function (operation) {\n      this.$http.post(this.$store.state.config.devPortalBackendUrl + '/applications/' + this.app.id + '/' + operation + '_heart', null, {headers: {Authorization: 'Bearer ' + this.$store.state.secure.accessToken}}).then(response => {\n        if (operation === 'add') {\n          this.hearts++\n          this.hearted = true\n        } else {\n          this.hearts--\n          this.hearted = false\n        }\n        this.build_hearts_class()\n      }, response => {\n        console.error(response)\n        if (operation === 'add') {\n          this.hearted = false\n        } else {\n          this.hearted = true\n        }\n        this.build_hearts_class()\n      })\n    },\n    toggle_heart_button_state: function () {\n      if (this.$store.state.secure.accessToken !== null) {\n        if (this.hearted) {\n          this.change_heart('remove')\n        } else {\n          this.change_heart('add')\n        }\n        this.build_hearts_class()\n      }\n    },\n    build_hearts_class: function () {\n      if (this.$store.state.secure.accessToken !== '' && this.$store.state.secure.accessToken != null) {\n        if (this.hearted) {\n          this.heartClass = 'btn btn-outline-secondary btn-thumbs-up active'\n        } else {\n          this.heartClass = 'btn btn-outline-secondary btn-thumbs-up'\n        }\n      } else {\n        this.heartClass = 'btn btn-outline-secondary btn-thumbs-up disabled'\n      }\n    }\n  },\n  watch: {\n    'app' (to, from) {\n      this.hearts = this.app.hearts\n    }\n  },\n  beforeMount: function () {\n    if (this.app.hearts !== undefined) {\n      this.hearts = this.app.hearts\n    }\n    this.get_user_data(this.$route.params.id)\n  }\n}\n</script>\n\n<style lang=\"scss\">\n// Title bar displayed below app banner\n\n.app-title-bar {\n    display: flex !important;\n    flex-direction: row;\n\n    img {\n      border-radius: 4px;\n      width: 42px;\n      min-width: 42px;\n      height: 42px;\n      margin-right: 5px;\n    }\n    // Author name and app title text container\n    .title-author {\n      height: 45px;\n      margin-top: -3px;\n      margin-bottom: 0;\n      display: inline-block;\n      max-width: 493px;\n      text-overflow: ellipsis;\n      overflow: hidden;\n      white-space: nowrap;\n      /*@media screen and (max-width: 430px) {\n        width: calc(100% - 160px);\n      }*/\n      //width: calc(100% - 220px);\n      width: 100%;\n\n      h1 {\n        font-size: 19px;\n        line-height: 26px;\n        display: inline-block;\n        color: #fff;\n        margin-bottom: 0;\n      }\n      h2 {\n        line-height: 16px;\n        font-size: 16px;\n        color: #c3c3c3;\n        margin: 0;\n      }\n\n      // Styles for small screens\n      @media screen and (max-width: 430px) {\n        h1 {\n          font-size: 15px;\n        }\n        h2 {\n          font-size: 12px;\n        }\n      }\n      @media screen and (max-width: 320px) {\n        h1 {\n          font-size: 12px;\n        }\n        h2 {\n          font-size: 10px;\n        }\n      }\n    }\n\n    // Set styles of buttons in the app-button-container (app-details and app-versions page)\n    .app-button-container {\n      margin-left: auto;\n      padding-left: 5px;\n      margin-top: 2px;\n      margin-bottom: 2px;\n      min-width: 206px;\n      text-align: right;\n      @media screen and (max-width: 430px) {\n        min-width: 156px;\n      }\n      .btn {\n        @media screen and (max-width: 430px) {\n          // styles for when screen smaller than 430px to avoid breaking all styles (they make things smaller)\n          margin-top: 3px;\n          font-size: 0.7rem;\n          padding: .5rem .5rem;\n        }\n        &.btn-download {\n          margin-left: 2px;\n        }\n\n        // Set thumbs up button styles\n        &.btn-thumbs-up {\n          color: #ccc;\n          border-color: #ccc;\n          cursor: hand;\n          // Styles for when it is in focus, hovered, or active\n          &:hover, &:active {\n            color: #ccc;\n            border-color: #ccc;\n            background: none;\n          }\n          &.active {\n            color: #333;\n            outline: none;\n            background: #ccc;\n          }\n\n          &.disabled:hover {\n            // Don't change any style if disabled\n            color: #ccc;\n            background: none;\n          }\n        }\n      }\n    }\n}\n\n.card.subsection-extra {\n  padding-left: 30px !important;\n  padding-right: 30px !important;\n  display: flex;\n  flex-direction: row;\n  h2 {\n    font-size: 16px;\n    line-height: 26px;\n    display: inline-block;\n    margin: 0;\n  }\n  .app-icon {\n    margin-top: -4px;\n    width: 22px;\n    vertical-align: middle;\n    color: #333;\n    fill: currentColor\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/pages/widgets/CardCollection.vue",
    "content": "<template>\n  <section class=\"text-center\">\n    <div class=\"header\" v-if=\"showTop\">\n      <h6 class=\"text-left\">\n        <div class=\"pebble\">{{ elTitle }}</div>\n      </h6>\n      <small><router-link v-bind:to=\"allUrl\" class=\"text-right\">See All ></router-link></small>\n    </div>\n    <div class=\"card-columns\">\n      <single-card v-for=\"(card, index) in cards\" v-bind:card=\"card\" v-bind:key=\"index\" v-bind:searchData=\"searchData\"></single-card>\n    </div>\n  </section>\n</template>\n\n<script>\nimport SingleCard from './SingleCard'\n\nexport default {\n  name: 'card-collection',\n  props: {\n    elTitle: {\n      type: String,\n      default: 'Collection Title'\n    },\n    showTop: {\n      type: Boolean,\n      default: true\n    },\n    cards: {\n      cards: []\n    },\n    allUrl: {\n      type: String,\n      default: ''\n    },\n    searchData: {\n      type: Boolean,\n      default: false\n    }\n  },\n  components: {\n    SingleCard\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n    // Each group of cards is supposed to be a section\n    section {\n        // Add a margin to the bottom of each section\n        margin-bottom: 40px;\n        max-width: 850px;\n        margin-left: auto;\n        margin-right: auto;\n        &:last-child {\n            // Remove unnecesary margin from the last one\n            margin-bottom: 0;\n        }\n        // The header is the title of each section\n        .header {\n            height: 30px;\n            margin-bottom: 10px;\n            h6 {\n                float: left;\n                display: inline;\n                margin-right: 10px;\n                margin-bottom: 0;\n            }\n            a {\n                color: #696969;\n                float: right;\n                &:hover, &:focus {\n                text-decoration: none;\n                outline: none;\n                }\n            }\n        }\n        .card-columns {\n            // Modify bootstrap's default .card-columns styles\n            max-width: 800px;\n            margin-left: auto;\n            margin-right: auto;\n            margin-bottom: -12px;\n\n            // Add more columns on large screens\n            @media screen and (min-width: map-get($grid-breakpoints, lg)) {\n                column-count: 4;\n            }\n\n            // Remove gaps on smaller screens\n            @media screen and (max-width: map-get($grid-breakpoints, sm)) {\n                column-gap: 5px;\n            }\n\n            column-count: 3;\n            column-gap: calc(1.25rem + 5px);\n\n        }\n    }\n\n</style>\n"
  },
  {
    "path": "src/components/pages/widgets/GetAppButton.vue",
    "content": "<template>\n  <span>\n    <a v-bind:href=\"'pebble://appstore/' + app.id\" class=\"btn btn-outline-pebble btn-download\" v-if=\"$store.state.userParameters.inApp !== true && ($store.state.userParameters.platform === 'all' || app.compatibility[$store.state.userParameters.platform].supported === true)\">\n      <svg class=\"svg-icon icon-download\" width=\"25px\" height=\"25px\" viewBox=\"0 0 25 25\">\n        <use xlink:href=\"#iconDownload\"></use>\n      </svg>\n      GET\n    </a>\n    <button v-on:click=\"check_app\" class=\"btn btn-outline-pebble btn-download\" :class=\"added || loading ? 'active': ''|| (added === null || !hardwareSupported || !platformSupported)?'disabled':''\" v-if=\"$store.state.userParameters.inApp === true\">\n      <svg class=\"svg-icon icon-download\" width=\"25px\" height=\"25px\" viewBox=\"0 0 25 25\">\n        <use xlink:href=\"#iconDownload\"></use>\n      </svg>\n      {{(loading)?'...':'GET'}}\n    </button>\n    <b-modal id=\"modal-permissions\" centered title=\"Pebble Permissions\" ok-title=\"Accept\"  cancel-title=\"Reject\" @ok=\"add_app()\">\n      <p>\"{{app.title}}\" is requesting access to the following services.</p>\n      <b-list-group class=\"my-4\">\n        <b-list-group-item v-for=\"(item, index) in permissions\" :key=\"index\">{{ item }}</b-list-group-item>\n      </b-list-group>\n      <p>If you tap on accept you will grant \"{{app.title}}\" access to read and use your data.</p>\n    </b-modal>\n    <b-modal id=\"modal-companion\" centered title=\"Companion Required\" ok-title=\"Get\"  cancel-title=\"Cancel\" @ok=\"get_companion()\">\n      <p class=\"my-4\">\"{{app.title}}\" requires a companion a to be used.</p>\n    </b-modal>\n  </span>\n</template>\n\n<script>\nimport { Native } from '../../../services'\n\nexport default {\n  name: 'get-app-button',\n  components: {\n  },\n  props: {\n    app: {\n      default: null\n    },\n    state: {\n      default: null\n    }\n  },\n  data: function () {\n    return {\n      loading: false,\n      added: false,\n      hardwareSupported: false,\n      platformSupported: false,\n      permissions: []\n    }\n  },\n  methods: {\n    add_app () {\n      this.loading = true\n      Native.send('loadAppToDeviceAndLocker', {\n        id: this.app.id,\n        uuid: this.app.uuid,\n        title: this.app.title,\n        list_image: this.app.list_image && this.app.list_image['144x144'],\n        icon_image: this.app.icon_image && this.app.icon_image['48x48'],\n        screenshot_image: this.app.screenshot_images && this.app.screenshot_images[0] && this.app.screenshot_images[0][Object.keys(this.app.screenshot_images[0])[0]],\n        type: this.app.type,\n        pbw_file: this.app.latest_release.pbw_file,\n        links: {\n          add: this.$store.state.backendUrl + '/applications/' + this.app.id + '/add',\n          remove: this.$store.state.backendUrl + '/applications/' + this.app.id + '/remove',\n          share: 'http://apps.rebble.io/app/' + this.app.id\n        }\n      }, (res) => {\n        if (res.added_to_locker) {\n          this.loading = false\n          this.added = true\n        }\n      })\n    },\n    get_companion () {\n      this.openExternal(this.app.companions[this.$store.state.userParameters.platform].url)\n    },\n    check_app () {\n      if (!this.hardwareSupported || !this.platformSupported || this.added === true || this.loading) return\n      if (this.permissions.length > 0) {\n        this.$bvModal.show('modal-permissions')\n        return\n      }\n      if (this.app.type === 'companion-app') {\n        this.$bvModal.show('modal-companion')\n        return\n      }\n      this.add_app()\n    },\n    check_supported () {\n      this.hardwareSupported = this.$store.state.userParameters.hardware === 'all' || (this.app.compatibility && this.app.compatibility[this.$store.state.userParameters.hardware] !== undefined && this.app.compatibility[this.$store.state.userParameters.hardware].supported === true)\n      this.platformSupported = this.$store.state.userParameters.platform === 'all' || this.app.compatibility[this.$store.state.userParameters.platform].supported\n    },\n    create_permissions () {\n      if (this.app.capabilities != null) {\n        this.permissions = this.app.capabilities\n        this.permissions.splice(this.permissions.indexOf('configurable'))\n      }\n    }\n  },\n  watch: {\n    'state' (to, from) {\n      this.added = this.state\n    },\n    'app' (to, from) {\n      this.check_supported()\n      this.create_permissions()\n    }\n  },\n  beforeMount: function () {\n    this.added = this.state\n    if (this.app !== null) {\n      this.check_supported()\n      this.create_permissions()\n    }\n  }\n}\n</script>\n\n<style lang='scss' scoped>\n</style>\n"
  },
  {
    "path": "src/components/pages/widgets/HomeSlider.vue",
    "content": "<template>\n  <header class=\"main\">\n    <div id=\"banner-carousel\" class=\"carousel slide\" data-ride=\"carousel\"  v-if=\"banners != null && banners[0] != null\">\n      <ol class=\"carousel-indicators\">\n        <li v-for=\"(banner, index) in banners\" v-bind:key=\"index\" data-target=\"#banner-carousel\" v-bind:data-slide-to=\"index\" v-bind:class=\"index == 0 ? 'active' : ''\"></li>\n      </ol>\n      <div class=\"carousel-inner\" role=\"listbox\">\n        <div v-bind:class=\"index == 0 ? 'carousel-item active' : 'carousel-item'\" v-for=\"(banner, index) in banners\" v-bind:key=\"index\"><router-link v-bind:alt=\"banner.title\" v-bind:to=\"'/app/' + banner.application_id\"><single-banner v-bind:bannerSrc=\"banner.image['720x320']\"></single-banner></router-link></div>\n      </div>\n      <a class=\"carousel-control carousel-control-prev\" href=\"#banner-carousel\" role=\"button\" data-slide=\"prev\">\n        <i class=\"fa fa-angle-left\" aria-hidden=\"true\"></i>\n        <span class=\"sr-only\">Previous</span>\n      </a>\n      <a class=\"carousel-control carousel-control-next\" href=\"#banner-carousel\" role=\"button\" data-slide=\"next\">\n        <i class=\"fa fa-angle-right\" aria-hidden=\"true\"></i>\n        <span class=\"sr-only\">Next</span>\n      </a>\n    </div>\n  </header>\n</template>\n\n<script>\nimport SingleBanner from './SingleBanner'\n\nexport default {\n  name: 'slider',\n  components: {\n    SingleBanner\n  },\n  props: {\n    banners: null\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n  .carousel {\n    max-width: 720px;\n    max-height: 320px;\n    margin-left: auto;\n    margin-right: auto;\n    background-color: #000;\n    box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12);\n\n    .carousel-indicators {\n      // Slide indicators, make them smaller with browser resize\n      margin-bottom: 0;\n      margin-left: 30%;\n      margin-right: 30%;\n\n    }\n\n    .carousel-control {\n      i {\n        // carousel control < > icons\n        font-size: 30px;\n      }\n    }\n\n    .carousel-item.active {\n      display: block;\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/components/pages/widgets/Pagination.vue",
    "content": "<template>\n  <nav v-if=\"links != undefined\">\n    <ul class=\"pagination\">\n      <li v-bind:class=\"this.$route.params.page > 1 ? 'page-item': 'page-item disabled'\">\n        <router-link class=\"page-link\" v-bind:to=\"`${baseUrl}/${+(this.$route.params.page) - 1}`\" tabindex=\"-1\" aria-label=\"Previous\">\n          <span aria-hidden=\"true\"><i class=\"fa fa-angle-left\" aria-hidden=\"true\"></i></span> Previous\n          <span class=\"sr-only\">Previous</span>\n        </router-link>\n      </li>\n      <li class=\"page-item\" v-bind:class=\"links.nextPage != null ? 'page-item': 'page-item disabled'\">\n        <router-link class=\"page-link\" v-bind:to=\"`${baseUrl}/${+(this.$route.params.page) + 1}`\" aria-label=\"Next\">\n          Next <span aria-hidden=\"true\"><i class=\"fa fa-angle-right\" aria-hidden=\"true\"></i></span>\n          <span class=\"sr-only\">Next</span>\n        </router-link>\n      </li>\n    </ul>\n  </nav>\n</template>\n\n<script>\nexport default {\n  name: 'pagination',\n  components: {\n  },\n  props: {\n    links: {\n      default: undefined\n    },\n    baseUrl: {\n      default: ''\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n</style>\n"
  },
  {
    "path": "src/components/pages/widgets/ScreenshotList.vue",
    "content": "<template>\n  <div v-dragscroll.x=\"!$store.state.userParameters.inApp\" class=\"screenshots\">\n      <div id=\"scrollbar\" v-bind:style=\"scrollStyle\">\n          <single-screenshot v-for=\"(screenshot, index) in screenshots\" v-bind:key=\"index\" v-bind:screenshotSrc=\"screenshot\"></single-screenshot>\n      </div>\n  </div>\n</template>\n\n<script>\nimport {dragscroll} from 'vue-dragscroll'\nimport SingleScreenshot from './SingleScreenshot'\n\nexport default {\n  name: 'ScreenshotList',\n  directives: {\n    dragscroll\n  },\n  components: {\n    SingleScreenshot\n  },\n  props: ['screenshots'],\n  computed: {\n    scrollStyle: function () {\n      var screenshotsCount = this.screenshots.length\n      return {'width': 'calc(100% + (' + screenshotsCount + ' - 1) * 184px)'}\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\">\n// Screenshots slider\n.screenshots {\n    width: 100%;\n    margin-bottom: 30px;\n    padding-bottom: 10px;\n    overflow-x: scroll;\n    overflow-y: hidden;\n    -webkit-overflow-scrolling: touch;\n\n    &::-webkit-scrollbar {\n        background: transparent;\n        width: 0 !important;\n    }\n}\n\n#scrollbar {\n    display: inline-block;\n    padding-left: calc(50% - 92px);\n}\n\n#scrollbar .screenshot {\n    float: left;\n}\n\n</style>\n"
  },
  {
    "path": "src/components/pages/widgets/SingleBanner.vue",
    "content": "<template>\n    <div class=\"app-banner\" v-images-loaded:on.done=\"loaded\">\n        <img v-show=\"bannerSrc && imageLoaded\" v-bind:src=\"bannerSrc\" alt=\"Banner\" />\n        <vcl-banner v-show=\"!bannerSrc || !imageLoaded\" class=\"loader\"></vcl-banner>\n    </div>\n</template>\n\n<script>\n\nimport VclBanner from './content-loaders/SingleBanner'\nimport imagesLoaded from 'vue-images-loaded'\n\nexport default {\n  name: 'SingleBanner',\n  directives: {\n    imagesLoaded\n  },\n  components: {\n    VclBanner\n  },\n  props: [\n    'bannerSrc'\n  ],\n  watch: {\n    'bannerSrc': function () {\n      this.imageLoaded = false\n    }\n  },\n  data: function () {\n    return {\n      'imageLoaded': false\n    }\n  },\n  methods: {\n    loaded: function (instance) {\n      this.imageLoaded = true\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.loader {\n  width: 720px;\n  height: 320px;\n}\n\nimg {\n    user-drag: none;\n    user-select: none;\n    -moz-user-select: none;\n    -webkit-user-drag: none;\n    -webkit-user-select: none;\n    -ms-user-select: none;\n}\n\n</style>\n"
  },
  {
    "path": "src/components/pages/widgets/SingleCard.vue",
    "content": "<template>\n  <div v-bind:class=\"imageLoaded ? 'loaded' : 'loading'\">\n    <vcl-card class=\"loader\"></vcl-card>\n    <router-link class=\"real-card\" v-bind:to=\"'/app/' + card.id\" v-images-loaded=\"loaded\">\n      <div class=\"card\" :class=\"$store.state.userParameters.hardware == 'chalk' ? 'round' : ''\">\n        <img class=\"card-img-top\" v-bind:src=\"card.screenshot_images[0][Object.keys(card.screenshot_images[0])[0]]\" alt=\"App Icon\">\n        <div class=\"card-block text-xs-center\">\n          <h6 class=\"card-title\">{{ card.title }}</h6>\n          <p class=\"card-text\">\n            <small class=\"text-muted\">\n              <svg class=\"svg-icon icon-inverted-thumbs-up\" width=\"16px\" height=\"16px\" viewBox=\"0 0 25 25\">\n                <use xlink:href=\"#iconThumbsUp\"></use>\n              </svg>\n              {{ card.hearts }}\n            </small>\n          </p>\n        </div>\n      </div>\n    </router-link>\n  </div>\n</template>\n\n<script>\nimport VclCard from './content-loaders/SingleCard'\nimport imagesLoaded from 'vue-images-loaded'\n\nexport default {\n  name: 'single-card',\n  directives: {\n    imagesLoaded\n  },\n  components: {\n    VclCard\n  },\n  props: {\n    card: {\n      id: '',\n      title: '',\n      type: '',\n      screenshot_images: [],\n      thumbs_up: 0\n    },\n    searchData: false\n  },\n  watch: {\n    card: function () {\n      this.imageLoaded = false\n      if (this.searchData) {\n        this.build_from_search()\n      }\n    }\n  },\n  data: function () {\n    return {\n      'imageLoaded': false\n    }\n  },\n  methods: {\n    loaded: function (instance) {\n      this.imageLoaded = true\n    },\n    build_from_search: function () {\n      // Identify platform and assign one screenshot in the right format\n      let hardware = this.$store.state.userParameters.hardware\n      let thisAssetCollection = this.card.asset_collections.find(function (assetCollection) {\n        return assetCollection.hardware_platform === hardware\n      })\n      if (thisAssetCollection == null) {\n        thisAssetCollection = this.card.asset_collections[0]\n      }\n      this.card.screenshot_images = [{'144x168': thisAssetCollection.screenshots[0]}]\n    }\n  },\n  beforeMount: function () {\n    if (this.searchData) {\n      this.build_from_search()\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n  div.loading {\n    .loader {\n      display: block;\n    }\n    .real-card {\n      display: none;\n    }\n  }\n  div.loaded {\n    .loader {\n      display: none;\n    }\n    .real-card {\n      display: block;\n    }\n  }\n    .loader {\n      max-width: 170px;\n      max-height: 253px;\n      margin-bottom: .75rem;\n      margin-left: auto;\n      margin-right: auto;\n      width: 100%;\n    }\n    a {\n\n        // Remove text decoration that comes from having the .card inside a \"a\"\n        color: #373a3c;\n        text-decoration: none;\n        &:hover, &:focus {\n            color: #373a3c;\n            outline: none;\n            text-decoration: none;\n        }\n\n        @media screen and (max-width: map-get($grid-breakpoints, lg)) {\n          // Remove 2 extra cards\n          &:last-child, &:nth-last-child(2) {\n            display: none;\n          }\n        }\n\n        .card {\n            max-width: 170px;\n            &.round {\n              border-top-left-radius: 50%;\n              border-top-right-radius: 50%;\n              .card-img-top {\n                border-radius: 50%;\n              }\n            }\n\n            .card-title {\n              text-overflow: ellipsis;\n              overflow: hidden;\n              white-space: nowrap;\n              margin: 7px 6px 5px 6px;\n            }\n\n            // Make it smaller on small screens\n            @media screen and (max-width: map-get($grid-breakpoints, sm)) {\n                max-width: 32vw;\n                display: inline-block;\n                margin-bottom: .75rem;\n                width: 100%;\n\n                .card-block {\n                    padding-left: 0.2rem;\n                    padding-right: 0.2rem;\n                }\n                .card-title {\n                    font-size: 16px;\n                }\n            }\n\n            // Make the app-screenshot take full-width\n            // TODO: decide what to do with app icons\n            img {\n                width:100%;\n                height:auto;\n            }\n        }\n    }\n</style>\n"
  },
  {
    "path": "src/components/pages/widgets/SingleScreenshot.vue",
    "content": "<template>\n    <div v-if=\"imageSize !== ''\" class=\"screenshot\" :class=\"$store.state.userParameters.hardware === 'chalk' ? 'round' : ''\" v-images-loaded:on.done=\"loaded\">\n        <img v-show=\"screenshotSrc[imageSize] && imageLoaded\" v-bind:src=\"screenshotSrc[imageSize]\" alt=\"Screenshot\" />\n        <vcl-screenshot-square v-if=\"$store.state.userParameters.hardware !== 'chalk'\" v-show=\"!screenshotSrc || !imageLoaded\" class=\"loader square\"></vcl-screenshot-square>\n        <vcl-screenshot-round v-if=\"$store.state.userParameters.hardware === 'chalk'\" v-show=\"!screenshotSrc || !imageLoaded\" class=\"loader round\"></vcl-screenshot-round>\n    </div>\n</template>\n\n<script>\n\nimport VclScreenshotSquare from './content-loaders/SingleScreenshotSquare'\nimport VclScreenshotRound from './content-loaders/SingleScreenshotRound'\nimport imagesLoaded from 'vue-images-loaded'\n\nexport default {\n  name: 'SingleScreenshot',\n  directives: {\n    imagesLoaded\n  },\n  components: {\n    VclScreenshotSquare,\n    VclScreenshotRound\n  },\n  props: [\n    'screenshotSrc'\n  ],\n  data: function () {\n    return {\n      'imageLoaded': false,\n      'imageSize': ''\n    }\n  },\n  methods: {\n    loaded: function (instance) {\n      this.imageLoaded = true\n    }\n  },\n  beforeMount: function () {\n    this.imageSize = Object.keys(this.screenshotSrc)[0]\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.loader {\n  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12);\n  margin-left: 20px;\n  margin-right: 20px;\n  &.square {\n    width: 144px;\n    height: 168px;\n  }\n  &.round {\n    width: 180px;\n    height: 180px;\n    border-radius: 50%\n  }\n}\n\n.round {\n  img {\n    border-radius: 50%;\n  }\n}\n\nimg {\n    box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12);\n    margin-left: 20px;\n    margin-right: 20px;\n    user-drag: none;\n    user-select: none;\n    -moz-user-select: none;\n    -webkit-user-drag: none;\n    -webkit-user-select: none;\n    -ms-user-select: none;\n}\n\n</style>\n"
  },
  {
    "path": "src/components/pages/widgets/TagList.vue",
    "content": "<template>\n  <div class=\"row tag-container\">\n    <div v-for=\"(tag, index) in tags\" v-bind:key=\"index\" class=\"col-6\"><router-link v-bind:to=\"'category/' + tag.slug\"  class=\"card text-white bg-dark text-center\">{{tag.name}}</router-link></div>\n  </div>\n</template>\n\n<script>\n\nexport default {\n  name: 'tag-list',\n  props: {\n    tags: {}\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.tag-container {\n  max-width: 850px;\n  margin-left: auto;\n  margin-right: auto;\n  .col-6 {\n    padding-left: 5px;\n    padding-right: 5px;\n    padding-top: 5px;\n    a:hover {\n      text-decoration: none;\n    }\n    .card {\n      font-family: 'Open Sans', sans-serif;\n      font-weight: 400;\n      border: 0;\n      padding: 6px;\n      border-radius: 4px;\n      box-shadow: 0 1px 1px 0 rgba(60,64,67,.08), 0 1px 3px 1px rgba(60,64,67,.16);\n    }\n  }\n  margin-bottom: 30px;\n}\n</style>\n"
  },
  {
    "path": "src/components/pages/widgets/content-loaders/SingleBanner.vue",
    "content": "<script>\nimport VueContentLoading from 'vue-content-loading'\nexport default {\n  components: {\n    VueContentLoading\n  }\n}\n</script>\n\n<template>\n  <vue-content-loading :width=\"720\" :height=\"320\" primary=\"#dddddd\" secondary=\"#bcbcbc\">\n    <rect x=\"0\" y=\"0\" width=\"720\" height=\"320\"/>\n  </vue-content-loading>\n</template>\n"
  },
  {
    "path": "src/components/pages/widgets/content-loaders/SingleCard.vue",
    "content": "<script>\nimport VueContentLoading from 'vue-content-loading'\nexport default {\n  components: {\n    VueContentLoading\n  }\n}\n</script>\n\n<template>\n  <vue-content-loading :width=\"170\" :height=\"253\" primary=\"#dddddd\" secondary=\"#bcbcbc\">\n    <path d=\"M170,196H0V4A4,4,0,0,1,4,0H166a4,4,0,0,1,4,4Z\"/>\n    <path d=\"M169,196v53a3,3,0,0,1-3,3H4a3,3,0,0,1-3-3V196H169m1-1H0v54a4,4,0,0,0,4,4H166a4,4,0,0,0,4-4V195Z\"/>\n    <rect x=\"29\" y=\"203\" width=\"106\" height=\"19\" rx=\"4\" ry=\"4\"/>\n    <rect x=\"65\" y=\"229\" width=\"34\" height=\"18\" rx=\"4\" ry=\"4\"/>\n  </vue-content-loading>\n</template>\n"
  },
  {
    "path": "src/components/pages/widgets/content-loaders/SingleScreenshotRound.vue",
    "content": "<script>\nimport VueContentLoading from 'vue-content-loading'\nexport default {\n  components: {\n    VueContentLoading\n  }\n}\n</script>\n\n<template>\n  <vue-content-loading :width=\"180\" :height=\"180\" primary=\"#dddddd\" secondary=\"#bcbcbc\">\n    <circle cx=\"90\" cy=\"90\" r=\"90\"/>\n  </vue-content-loading>\n</template>\n"
  },
  {
    "path": "src/components/pages/widgets/content-loaders/SingleScreenshotSquare.vue",
    "content": "<script>\nimport VueContentLoading from 'vue-content-loading'\nexport default {\n  components: {\n    VueContentLoading\n  }\n}\n</script>\n\n<template>\n  <vue-content-loading :width=\"144\" :height=\"168\" primary=\"#dddddd\" secondary=\"#bcbcbc\">\n    <rect x=\"0\" y=\"0\" width=\"144\" height=\"168\"/>\n  </vue-content-loading>\n</template>\n"
  },
  {
    "path": "src/css/_error.scss",
    "content": "// _error.scss\n// Error page styles\n\n\n.page-error {\n  // Calculate top margin 58 px ar of the navbar\n  margin-top: 40px + 58px;\n  min-height: 400px;\n  height: 80vh;\n  display: flex;\n  flex-direction: column;\n  max-width: 36rem;\n  margin-left: auto;\n  margin-right: auto;\n  align-items: center;\n  justify-content: center;\n\n  .pet-rock-pebble {\n      // Mascot svg\n      margin-top: 3rem;\n      margin-bottom: 3rem;\n    }\n\n    .page-error_buttons {\n      // Minimal style changes to buttons\n      margin-top: 3rem;\n      .btn-outline-pebble {\n        &:hover {\n          color: #f4f3f4;\n        }\n      }\n    }\n}\n"
  },
  {
    "path": "src/main.js",
    "content": "import Vue from 'vue'\nimport VueResource from 'vue-resource'\nimport BootstrapVue from 'bootstrap-vue'\n\nimport 'bootstrap/dist/css/bootstrap.css'\nimport 'bootstrap-vue/dist/bootstrap-vue.css'\n\nimport App from './App'\nimport router from './router'\nimport store from './store'\nimport mixin from './mixin'\n\nimport InstantSearch from 'vue-instantsearch'\nimport VueCookie from 'vue-cookie'\n\nimport CardCollection from './components/pages/widgets/CardCollection'\n\nVue.filter('formatDate', function (d) {\n  let date = new Date(d)\n  if (date) {\n    return date.getFullYear() + '-' + ((date.getMonth() + 1) >= 10 ? (date.getMonth() + 1) : ('0' + (date.getMonth() + 1))) + '-' + (date.getDate() >= 10 ? date.getDate() : ('0' + date.getDate()))\n  }\n})\n\nVue.filter('capitalize', function (value) {\n  if (!value) return ''\n  value = value.toString()\n  return value.charAt(0).toUpperCase() + value.slice(1)\n})\n\nVue.filter('readable-name', function (value) {\n  if (!value) return ''\n  value = value.toString()\n  return value[0].toUpperCase() + value.replace(new RegExp('-', 'g'), ' ').substring(1)\n})\n\nVue.mixin({\n  methods: {\n    buildResourceUrl: mixin.buildResourceUrl,\n    setTitle: mixin.setTitle,\n    openExternal: mixin.openExternal\n  }\n})\n\nVue.use(VueResource)\nVue.use(BootstrapVue)\n\nVue.use(InstantSearch)\nVue.use(VueCookie)\n\nVue.component('card-collection', CardCollection)\n\n/* eslint-disable no-new */\nnew Vue({\n  el: '#app',\n  router,\n  store,\n  beforeCreate () {\n    this.$store.commit('userParameters/INIT', null, { root: true })\n    this.$store.commit('secure/INIT', null, { root: true })\n    store.subscribe((mutation, state) => {\n      if (mutation.type.substr(0, 15) === 'userParameters/') {\n        // Only save user parameters when needed\n        localStorage.setItem('rebbleUserParameters', JSON.stringify(state.userParameters))\n      }\n      if (mutation.type.substr(0, 23) === 'secure/SET_ACCESS_TOKEN') {\n        // Only save user parameters when needed\n        this.$cookie.set('access_token', state.secure.accessToken)\n      }\n    })\n  },\n  render: h => h(App)\n})\n"
  },
  {
    "path": "src/mixin/index.js",
    "content": "import { Native } from '../services'\nimport { hardwareEnum } from '../store/userParameters'\n\nconst mixins = {\n  buildResourceUrl (resource) {\n    return `${this.$store.state.config.backendUrl}/${resource}?platform=${this.$store.state.userParameters.platform}${this.$store.state.userParameters.hardware !== hardwareEnum.all ? `&hardware=${this.$store.state.userParameters.hardware}&filter_hardware=true` : ''}`\n  },\n  setTitle (title = '') {\n    document.title = title === '' ? 'Rebble Store' : `${title} | Rebble Store`\n    if (this.$store.state.userParameters.inApp === true) {\n      Native.send('setNavBarTitle', { title: title })\n    }\n  },\n  openExternal (url) {\n    if (this.$store.state.userParameters.inApp === true) {\n      Native.send('openURL', {\n        url: url\n      })\n    } else {\n      window.open(url, '_blank')\n    }\n  }\n}\nexport default mixins\n"
  },
  {
    "path": "src/router/index.js",
    "content": "import qs from 'qs'\nimport Vue from 'vue'\nimport Router from 'vue-router'\nimport Home from '@/components/Home'\nimport Category from '@/components/pages/Category'\nimport AppView from '@/components/pages/AppView'\nimport AppDetails from '@/components/pages/AppDetails'\nimport AppVersions from '@/components/pages/AppVersions'\nimport Author from '@/components/pages/Author'\nimport Search from '@/components/pages/Search'\nimport Collection from '@/components/pages/Collection'\nimport Settings from '@/components/pages/Settings'\nimport Error from '@/components/pages/Error'\n\nVue.use(Router)\n\nconst routes = [\n  {path: '', redirect: '/faces'},\n  {\n    path: '/category/:id/:sort/:page',\n    component: Category\n  },\n  {\n    path: '/category/:id',\n    redirect: '/category/:id/hearts/1'\n  },\n  {\n    path: '/category/:id/:sort',\n    redirect: '/category/:id/:sort/1'\n  },\n  {\n    path: '/app/:id',\n    component: AppView,\n    children: [\n      {\n        path: '',\n        component: AppDetails\n      },\n      {\n        path: 'versions',\n        component: AppVersions\n      }\n    ]\n  },\n  {path: '/author/:id', redirect: '/author/:id/1'},\n  {path: '/author/:id/:page', component: Author},\n  {path: '/settings', component: Settings},\n  {path: '/:type', component: Home},\n  {\n    path: '/:type/search',\n    component: Search,\n    props: true\n  },\n  {path: '/:type/:slug', redirect: '/:type/:slug/1'},\n  {path: '/:type/:slug/:page', component: Collection},\n  {path: '*', component: Error}\n]\n\nexport default new Router({\n  mode: 'history',\n  routes: routes,\n  parseQuery (query) {\n    return qs.parse(query)\n  },\n  stringifyQuery (query) {\n    const result = qs.stringify(query)\n\n    return result ? '?' + result : ''\n  }\n})\n"
  },
  {
    "path": "src/router/search-router.js",
    "content": "import vueRouter from './index'\n\nexport const searchRouting = {\n  router: {\n    read () {\n      return vueRouter.currentRoute.query\n    },\n    write (routeState) {\n      vueRouter.push({\n        query: routeState\n      })\n    },\n    createURL (routeState) {\n      return vueRouter.resolve({\n        query: routeState\n      }).href\n    },\n    onUpdate (cb) {\n      this._onPopState = ({state}) => {\n        const routeState = state\n        // at initial load, the state is read from the URL without\n        // update. Therefore the state object is not there. In this\n        // case we fallback and read the URL.\n        if (!routeState) {\n          cb(this.read())\n        } else {\n          cb(routeState)\n        }\n      }\n      window.addEventListener('popstate', this._onPopState)\n    },\n    dispose () {\n      window.removeEventListener('popstate', this._onPopState)\n      this.write()\n    }\n  }\n}\n"
  },
  {
    "path": "src/services/index.js",
    "content": "import store from '../store'\nimport router from '../router'\n\nclass NativeService {\n  constructor () {\n    this.callbacks = []\n    this.callbackId = 0\n    this.methods = ['setNavBarTitle', 'openURL', 'addToLocker', 'loadAppToDeviceAndLocker', 'promptUserForAddToLockerOrLoad', 'getAppsFromLocker', 'removeFromLocker', 'isAppInLocker', 'unloadAppFromPebble', 'getLoadedAppsFromPebble', 'tryWatchface', 'isConnected', 'closeScreen', 'skipStep', 'bulkLoadAndClose', 'setVisibleApp', 'refreshAccessToken']\n    window.PebbleBridge = this\n  }\n\n  send (methodName, args, responseCallback, sendCallback) {\n    window.setTimeout(() => {\n      if (typeof methodName !== 'string') return this._sendError('Native: methodName is not an object', sendCallback)\n      if (!~this.methods.indexOf(methodName)) return this._sendError(`Native: ${methodName} is not in list of known methods`, sendCallback)\n      if (typeof args !== 'object') return this._sendError('Native: args is not an object', sendCallback)\n      // if (config.IS_BROWSER) return void $log.debug('Native: ' + methodName + ' not available in browser');\n      var _callbackId = -1\n      if (typeof responseCallback === 'function') {\n        this.callbacks.push(responseCallback)\n        _callbackId = this.callbackId\n        this.callbackId = this.callbackId + 1\n      } else {\n        responseCallback && this._sendError('Native: callback is not a function')\n      }\n      var uri = this._buildURI(methodName, _callbackId, args)\n      this._executeSend(uri)\n      if (typeof sendCallback === 'function') {\n        sendCallback(null, uri)\n      }\n    })\n  }\n\n  _executeSend (uri) {\n    let iframe = document.createElement('iframe')\n    iframe.setAttribute('src', uri)\n    iframe.setAttribute('height', '1px')\n    iframe.setAttribute('width', '1px')\n    document.documentElement.appendChild(iframe)\n    iframe.parentNode.removeChild(iframe)\n    iframe = null\n  }\n\n  _sendError (err, callback) {\n    console.error(err)\n    callback(err)\n  }\n\n  _buildURI (methodName, callbackId, args) {\n    let msg = this._encodeMsg(methodName, callbackId, args)\n    let protocol = 'pebble-method-call-js-frame://'\n    let queryCharacter = store.state.userParameters.platform === 'ios' ? '?' : ''\n    let uri = protocol + queryCharacter + 'method=' + methodName + '&args=' + msg\n    return uri\n  }\n\n  _encodeMsg (methodName, callbackId, args) {\n    let msgStringified\n    let msg = {\n      methodName: methodName,\n      callbackId: callbackId,\n      data: args\n    }\n    try {\n      msgStringified = JSON.stringify(msg)\n    } catch (e) {\n      return void console.error('Native: msg cannot be JSON encoded', e)\n    }\n    let msgURIEncoded\n    try {\n      msgURIEncoded = encodeURIComponent(msgStringified)\n    } catch (e) {\n      return void console.error('Native: msg cannot be URI encoded', e)\n    }\n    return msgURIEncoded\n  }\n\n  handleResponse (args) {\n    if (typeof args !== 'object' && args !== null) return void console.error('Native: args.methodName is not an object')\n    if (typeof args.data !== 'object') return void console.error('Native: args.data is not an object')\n    if (typeof args.callbackId !== 'number') return void console.error('Native: args.callbackId is not a number')\n    if (args.callbackId < 0) return\n    let callback = this.callbacks[args.callbackId]\n    delete this.callbacks[args.callbackId]\n    if (callback && typeof callback === 'function') {\n      callback(args.data)\n    } else {\n      console.error('Native: callback is not a function', callback)\n    }\n  }\n  _reload () {\n    window.location.reload(true)\n  }\n\n  handleRequest (args) {\n    if (typeof args !== 'object') return void console.error('Native: args.methodName is not an object')\n    if (typeof args.methodName !== 'string') return void console.error('Native: args.methodName is not an object')\n    switch (args.methodName) {\n      case 'search':\n        // let section = args.section || Storage.get('activeSection') || 'watchapps'\n        // let query = args.query || (Storage.get('searchData-' + section) || {}).query || ''\n        let section = args.section || 'apps'\n        let query = args.query || ''\n        let isNative = !(!args.query && !args.section)\n        let url = `/${section}/search?page=1&query=${encodeURIComponent(query)}${(isNative ? '&inApp=true' : '')}`\n        router.push(url)\n        break\n      case 'navigate':\n        router.push(args.url || '/')\n        break\n      case 'refresh':\n        this._reload()\n    }\n  }\n}\n\nexport const Native = new NativeService()\n"
  },
  {
    "path": "src/store/config.js",
    "content": "export default {\n  namespaced: true,\n  state: {\n    backendUrl: 'https://appstore-api.rebble.io/api/v1',\n    devPortalBackendUrl: 'https://appstore-api.rebble.io/api/v0',\n    tosLink: 'https://rebble.io/tos/',\n    devPortalLink: 'https://rebble.io/submit/',\n    contactLink: '',\n    accessToken: null\n  }\n}\n"
  },
  {
    "path": "src/store/index.js",
    "content": "import Vue from 'vue'\nimport Vuex from 'vuex'\nimport pathify from 'vuex-pathify'\nimport userParameters from './userParameters'\nimport config from './config'\nimport secure from './secure'\n\nVue.use(Vuex)\n\nexport default new Vuex.Store({\n  modules: {\n    userParameters: userParameters,\n    config: config,\n    secure: secure\n  },\n  plugins: [pathify.plugin]\n})\n"
  },
  {
    "path": "src/store/secure.js",
    "content": "import VueCookie from 'vue-cookie'\nimport { make } from 'vuex-pathify'\n\nconst state = {\n  accessToken: null\n}\n\nexport default {\n  namespaced: true,\n  state: state,\n  mutations: {\n    ...make.mutations(state),\n    INIT (state) {\n      if (VueCookie.get('access_token')) {\n        state.accessToken = VueCookie.get('access_token')\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/store/userParameters.js",
    "content": "import { make } from 'vuex-pathify'\nimport { version } from '../../package.json'\n\nexport const platformEnum = {\n  all: 'all',\n  ios: 'ios',\n  android: 'android'\n}\n\nexport const hardwareEnum = {\n  all: 'all',\n  aplite: 'aplite', // OG Pebble and Pebble Steel\n  basalt: 'basalt', // Pebble Time and Pebble Time Steel\n  chalk: 'chalk', // Pebble Round\n  diorite: 'diorite' // Pebble 2\n}\n\nconst state = {\n  version: '',\n  platform: platformEnum.all, // either 'android', 'ios', or 'all'\n  hardware: hardwareEnum.all,\n  appVersion: '',\n  inApp: false,\n  devMode: false\n}\n\nexport default {\n  namespaced: true,\n  state: state,\n  mutations: {\n    ...make.mutations(state),\n    INIT (state) {\n      if (localStorage.getItem('rebbleUserParameters')) {\n        let cacheState = JSON.parse(localStorage.getItem('rebbleUserParameters'))\n        if (cacheState.version === version) {\n          Object.assign(state, cacheState)\n        } else {\n          state.version = version\n        }\n      } else {\n        state.version = version\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "static/.gitkeep",
    "content": ""
  },
  {
    "path": "static/browserconfig.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<browserconfig>\n  <msapplication>\n    <tile>\n      <square150x150logo src=\"/static/mstile-150x150.png\"/>\n      <TileColor>#373a3c</TileColor>\n    </tile>\n  </msapplication>\n</browserconfig>\n"
  },
  {
    "path": "static/css/_variables.scss",
    "content": "// _varaibles.scss\n// Store all the variables in here\n\n// Bootstrap's grid breakpoints\n// Use them this way: map-get($grid-breakpoints, sm)\n$grid-breakpoints: (\n  xs: 0,\n  sm: 576px,\n  md: 768px,\n  lg: 992px,\n  xl: 1200px\n);\n\n// Pebble orange color\n$pebble-color: #ff4700;\n\n// Main Background color (originally a really light gray)\n$main-bg-color: #f4f3f4;\n"
  },
  {
    "path": "static/manifest.json",
    "content": "{\n\t\"name\": \"Rebble Store\",\n\t\"icons\": [\n\t\t{\n\t\t\t\"src\": \"\\/static\\/android-chrome-192x192.png\",\n\t\t\t\"sizes\": \"192x192\",\n\t\t\t\"type\": \"image\\/png\"\n\t\t},\n\t\t{\n\t\t\t\"src\": \"\\/static\\/android-chrome-384x384.png\",\n\t\t\t\"sizes\": \"384x384\",\n\t\t\t\"type\": \"image\\/png\"\n\t\t}\n\t],\n\t\"theme_color\": \"#ffffff\",\n\t\"display\": \"standalone\"\n}\n"
  },
  {
    "path": "test/e2e/custom-assertions/elementCount.js",
    "content": "// A custom Nightwatch assertion.\n// The assertion name is the filename.\n// Example usage:\n//\n//   browser.assert.elementCount(selector, count)\n//\n// For more information on custom assertions see:\n// http://nightwatchjs.org/guide#writing-custom-assertions\n\nexports.assertion = function (selector, count) {\n  this.message = 'Testing if element <' + selector + '> has count: ' + count\n  this.expected = count\n  this.pass = function (val) {\n    return val === this.expected\n  }\n  this.value = function (res) {\n    return res.value\n  }\n  this.command = function (cb) {\n    var self = this\n    return this.api.execute(function (selector) {\n      return document.querySelectorAll(selector).length\n    }, [selector], function (res) {\n      cb.call(self, res)\n    })\n  }\n}\n"
  },
  {
    "path": "test/e2e/nightwatch.conf.js",
    "content": "require('babel-register')\nvar config = require('../../config')\n\n// http://nightwatchjs.org/gettingstarted#settings-file\nmodule.exports = {\n  src_folders: ['test/e2e/specs'],\n  output_folder: 'test/e2e/reports',\n  custom_assertions_path: ['test/e2e/custom-assertions'],\n\n  selenium: {\n    start_process: true,\n    server_path: require('selenium-server').path,\n    host: '127.0.0.1',\n    port: 4444,\n    cli_args: {\n      'webdriver.chrome.driver': require('chromedriver').path\n    }\n  },\n\n  test_settings: {\n    default: {\n      selenium_port: 4444,\n      selenium_host: 'localhost',\n      silent: true,\n      globals: {\n        devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port)\n      }\n    },\n\n    chrome: {\n      desiredCapabilities: {\n        browserName: 'chrome',\n        javascriptEnabled: true,\n        acceptSslCerts: true\n      }\n    },\n\n    firefox: {\n      desiredCapabilities: {\n        browserName: 'firefox',\n        javascriptEnabled: true,\n        acceptSslCerts: true\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "test/e2e/runner.js",
    "content": "// 1. start the dev server using production config\nprocess.env.NODE_ENV = 'testing'\n\nconst webpack = require('webpack')\nconst DevServer = require('webpack-dev-server')\n\nconst webpackConfig = require('../../build/webpack.prod.conf')\nconst devConfigPromise = require('../../build/webpack.dev.conf')\n\nlet server\n\ndevConfigPromise.then(devConfig => {\n  const devServerOptions = devConfig.devServer\n  const compiler = webpack(webpackConfig)\n  server = new DevServer(compiler, devServerOptions)\n  const port = devServerOptions.port\n  const host = devServerOptions.host\n  return server.listen(port, host)\n})\n.then(() => {\n  // 2. run the nightwatch test suite against it\n  // to run in additional browsers:\n  //    1. add an entry in test/e2e/nightwatch.conf.js under \"test_settings\"\n  //    2. add it to the --env flag below\n  // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox`\n  // For more information on Nightwatch's config file, see\n  // http://nightwatchjs.org/guide#settings-file\n  let opts = process.argv.slice(2)\n  if (opts.indexOf('--config') === -1) {\n    opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js'])\n  }\n  if (opts.indexOf('--env') === -1) {\n    opts = opts.concat(['--env', 'chrome'])\n  }\n\n  const spawn = require('cross-spawn')\n  const runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' })\n\n  runner.on('exit', function (code) {\n    server.close()\n    process.exit(code)\n  })\n\n  runner.on('error', function (err) {\n    server.close()\n    throw err\n  })\n})\n"
  },
  {
    "path": "test/e2e/specs/test.js",
    "content": "// For authoring Nightwatch tests, see\n// http://nightwatchjs.org/guide#usage\n\nmodule.exports = {\n  'default e2e tests': function (browser) {\n    // automatically uses dev Server port from /config.index.js\n    // default: http://localhost:8080\n    // see nightwatch.conf.js\n    const devServer = browser.globals.devServerURL\n\n    browser\n      .url(devServer)\n      .waitForElementVisible('#app', 5000)\n      .end()\n  }\n}\n"
  },
  {
    "path": "test/unit/.eslintrc",
    "content": "{\n  \"env\": { \n    \"mocha\": true\n  },\n  \"globals\": { \n    \"expect\": true,\n    \"sinon\": true\n  }\n}\n"
  },
  {
    "path": "test/unit/index.js",
    "content": "import Vue from 'vue'\n\nVue.config.productionTip = false\n\n// require all test files (files that ends with .spec.js)\nconst testsContext = require.context('./specs', true, /\\.spec$/)\ntestsContext.keys().forEach(testsContext)\n\n// require all src files except main.js for coverage.\n// you can also change this to match only the subset of files that\n// you want coverage for.\nconst srcContext = require.context('../../src', true, /^\\.\\/(?!main(\\.js)?$)/)\nsrcContext.keys().forEach(srcContext)\n"
  },
  {
    "path": "test/unit/karma.conf.js",
    "content": "// This is a karma config file. For more details see\n//   http://karma-runner.github.io/0.13/config/configuration-file.html\n// we are also using it with karma-webpack\n//   https://github.com/webpack/karma-webpack\n\nvar webpackConfig = require('../../build/webpack.test.conf')\n\nmodule.exports = function karmaConfig (config) {\n  config.set({\n    // to run in additional browsers:\n    // 1. install corresponding karma launcher\n    //    http://karma-runner.github.io/0.13/config/browsers.html\n    // 2. add it to the `browsers` array below.\n    browsers: ['PhantomJS'],\n    frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'],\n    reporters: ['spec', 'coverage'],\n    files: ['./index.js'],\n    preprocessors: {\n      './index.js': ['webpack', 'sourcemap']\n    },\n    webpack: webpackConfig,\n    webpackMiddleware: {\n      noInfo: true\n    },\n    coverageReporter: {\n      dir: './coverage',\n      reporters: [\n        { type: 'lcov', subdir: '.' },\n        { type: 'text-summary' }\n      ]\n    }\n  })\n}\n"
  }
]