├── README.md ├── .browserslistrc ├── .env.dev.gpg ├── .env.prd.gpg ├── public ├── favicon.ico ├── favicon_256.png ├── favicon_32.png ├── favicon_48.png ├── favicon_512.png ├── main-visual.png └── index.html ├── babel.config.js ├── jest.config.js ├── src ├── secrets │ └── secrets.ts ├── shims-vue.d.ts ├── utils │ ├── TimeMapRequest.ts │ ├── defines.ts │ ├── i18n.ts │ ├── utils.ts │ └── darkStyle.ts ├── router │ └── index.ts ├── components │ ├── modules │ │ ├── TransparentBack.vue │ │ ├── LoadingBack.vue │ │ └── ModalLink.vue │ ├── layouts │ │ └── TheFooter.vue │ └── parts │ │ ├── LoadSpinner.vue │ │ └── SvgIcon.vue ├── App.vue ├── @types │ └── vue-toaster.d.ts ├── store │ └── index.ts ├── views │ ├── DefaultMapView.vue │ └── MapView.vue ├── main.ts └── scss │ ├── toast.css │ └── _css.scss ├── vue.config.js ├── .gitignore ├── .eslintrc.js ├── tsconfig.json ├── LICENSE ├── .github └── workflows │ └── deploy.yaml └── package.json /README.md: -------------------------------------------------------------------------------- 1 | 🍊 2 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /.env.dev.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirumirumi/how-far-can-i-go/HEAD/.env.dev.gpg -------------------------------------------------------------------------------- /.env.prd.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirumirumi/how-far-can-i-go/HEAD/.env.prd.gpg -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirumirumi/how-far-can-i-go/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /public/favicon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirumirumi/how-far-can-i-go/HEAD/public/favicon_256.png -------------------------------------------------------------------------------- /public/favicon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirumirumi/how-far-can-i-go/HEAD/public/favicon_32.png -------------------------------------------------------------------------------- /public/favicon_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirumirumi/how-far-can-i-go/HEAD/public/favicon_48.png -------------------------------------------------------------------------------- /public/favicon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirumirumi/how-far-can-i-go/HEAD/public/favicon_512.png -------------------------------------------------------------------------------- /public/main-visual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirumirumi/how-far-can-i-go/HEAD/public/main-visual.png -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: '@vue/cli-plugin-unit-jest/presets/typescript-and-babel', 3 | transform: { 4 | '^.+\\.vue$': 'vue-jest' 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/secrets/secrets.ts: -------------------------------------------------------------------------------- 1 | export const apiKeyGoogle = process.env.VUE_APP_API_KEY_GOOGLE 2 | export const appId = process.env.VUE_APP_API_KEY_APPID 3 | export const apiKeyTT = process.env.VUE_APP_API_KEY_TT 4 | -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | declare module '*.vue' { 3 | import type { DefineComponent } from 'vue' 4 | const component: DefineComponent<{}, {}, any> 5 | export default component 6 | } 7 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | css: { 3 | loaderOptions: { 4 | sass: { 5 | prependData: `@import "@/scss/_css.scss";`, 6 | }, 7 | }, 8 | }, 9 | publicPath: process.env.NODE_ENV === "prd" 10 | ? "/apps/how-far-map" 11 | : "/", 12 | } 13 | -------------------------------------------------------------------------------- /src/utils/TimeMapRequest.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | departure_searches: [ 3 | { 4 | id: "", 5 | coords: { 6 | lat: 0, 7 | lng: 0 8 | }, 9 | travel_time: 0, 10 | transportation: { 11 | type: "" 12 | }, 13 | departure_time: "2222-02-22T22:22:22.222Z", 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router" 2 | 3 | const routes: Array = [ 4 | { 5 | path: "/", 6 | name: "MapView", 7 | component: () => import("../views/MapView.vue"), 8 | }, 9 | ] 10 | 11 | const router = createRouter({ 12 | history: createWebHistory(process.env.BASE_URL), 13 | routes 14 | }) 15 | 16 | export default router 17 | -------------------------------------------------------------------------------- /src/utils/defines.ts: -------------------------------------------------------------------------------- 1 | export const MAX_LOAD_MAP_LIMIT = 63 2 | export const MAX_GEOCODE_LIMIT = 21 3 | 4 | export interface LatLng { 5 | lat: number, 6 | lng: number, 7 | } 8 | 9 | export enum SystemThemeConfig { 10 | LIGHT = "light", 11 | DARK = "dark", 12 | OS_Sync = "os", 13 | } 14 | 15 | export enum Transportation { 16 | WALKING = "walking", 17 | CYCLING = "cycling", 18 | DRIVING = "driving", 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env.* 2 | !*.gpg 3 | .DS_Store 4 | node_modules 5 | /dist 6 | not_used/ 7 | *.md 8 | 9 | # ----------------------------------- # 10 | 11 | # local env files 12 | .env.local 13 | .env.*.local 14 | 15 | # Log files 16 | npm-debug.log* 17 | yarn-debug.log* 18 | yarn-error.log* 19 | pnpm-debug.log* 20 | 21 | # Editor directories and files 22 | .idea 23 | .vscode 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | -------------------------------------------------------------------------------- /src/components/modules/TransparentBack.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 16 | 17 | 31 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 17 | 18 | 30 | -------------------------------------------------------------------------------- /src/components/layouts/TheFooter.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 13 | 14 | 33 | -------------------------------------------------------------------------------- /src/components/parts/LoadSpinner.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 21 | 22 | 24 | -------------------------------------------------------------------------------- /src/components/modules/LoadingBack.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 18 | 19 | 37 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true, 5 | // 'vue/setup-compiler-macros': true, 6 | }, 7 | globals: { 8 | defineProps: "readonly", 9 | defineEmits: "readonly", 10 | defineExpose: "readonly", 11 | withDefaults: "readonly", 12 | }, 13 | 'extends': [ 14 | 'plugin:vue/vue3-essential', 15 | 'eslint:recommended', 16 | '@vue/typescript/recommended' 17 | ], 18 | parserOptions: { 19 | ecmaVersion: 2020 20 | }, 21 | rules: { 22 | 'no-console': process.env.NODE_ENV === 'prd' ? 'warn' : 'off', 23 | 'no-debugger': process.env.NODE_ENV === 'prd' ? 'warn' : 'off', 24 | "no-useless-escape": 0, 25 | "no-irregular-whitespace": 0, 26 | "@typescript-eslint/no-extra-semi": 0, 27 | "@typescript-eslint/no-explicit-any": 0, 28 | "@typescript-eslint/no-var-requires": 0, 29 | "@typescript-eslint/no-non-null-assertion": 0, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "skipLibCheck": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "allowJs": true, 13 | "outDir": "./dist", // https://bit.ly/3zKgDq3 14 | "sourceMap": true, 15 | "baseUrl": ".", 16 | "types": [ 17 | "webpack-env", 18 | "jest" 19 | ], 20 | "paths": { 21 | "@/*": [ 22 | "src/*" 23 | ] 24 | }, 25 | "lib": [ 26 | "esnext", 27 | "dom", 28 | "dom.iterable", 29 | "scripthost" 30 | ] 31 | }, 32 | "include": [ 33 | "src/**/*.ts", 34 | "src/**/*.tsx", 35 | "src/**/*.vue", 36 | "tests/**/*.ts", 37 | "tests/**/*.tsx" 38 | ], 39 | "exclude": [ 40 | "node_modules" 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /src/@types/vue-toaster.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@/components/modules/vue-toaster' { 2 | export const install: PluginFunction<{}>; // eslint-disable-line 3 | 4 | export interface ToasterOptions { 5 | message?: string; 6 | type?: 'default' | 'success' | 'info' | 'warning' | 'error'; 7 | position?: 'top' | 'bottom' | 'top-right' | 'bottom-right' | 'top-left' | 'bottom-left'; 8 | duration?: number; 9 | dismissible?: boolean; 10 | onClick?(): void; 11 | onClose?(): void; 12 | queue?: boolean; 13 | pauseOnHover?: boolean; 14 | useDefaultCss?: boolean; 15 | } 16 | export = class Toaster { 17 | clear(): void; 18 | 19 | show(message: string, options?: ToasterOptions): void; 20 | success(message: string, options?: ToasterOptions): void; 21 | error(message: string, options?: ToasterOptions): void; 22 | info(message: string, options?: ToasterOptions): void; 23 | warning(message: string, options?: ToasterOptions): void; 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 mirumi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { InjectionKey } from "vue" 2 | import { createStore, Store, useStore as baseUseStore } from "vuex" 3 | import { Transportation } from "@/utils/defines" 4 | 5 | export interface State { 6 | query: string, 7 | transportation: Transportation | string, 8 | time: number, 9 | walkingSpeed: number, 10 | } 11 | 12 | export const store = createStore({ 13 | state: { 14 | query: "", 15 | transportation: Transportation.WALKING, 16 | time: 10, 17 | walkingSpeed: 80, 18 | }, 19 | getters: { 20 | }, 21 | mutations: { 22 | setQuery(state, query: string) { 23 | state.query = query 24 | }, 25 | setTransportation(state, tp: Transportation) { 26 | state.transportation = tp 27 | }, 28 | setTime(state, time: number) { 29 | state.time = time 30 | }, 31 | setWalkingSpeed(state, speed: number) { 32 | state.walkingSpeed = speed 33 | }, 34 | }, 35 | actions: { 36 | }, 37 | modules: { 38 | } 39 | }) 40 | 41 | export const key: InjectionKey> = Symbol(); 42 | export const useStore = () => { // eslint-disable-line 43 | return baseUseStore(key) 44 | } 45 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yaml: -------------------------------------------------------------------------------- 1 | name: deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - release/prd 7 | workflow_dispatch: 8 | 9 | jobs: 10 | deploy: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-node@v2.4.1 15 | with: 16 | node-version: "14" 17 | - run: npm install 18 | - name: gpg 19 | run: | 20 | echo "${{ secrets.GPG_PASSPHRASE }}" | sudo gpg --batch --passphrase-fd 0 --output ".env.prd" --decrypt ".env.prd.gpg" 21 | - run: npm run build-prd 22 | - name: prd deploy 23 | run: | 24 | mkdir -p ~/.ssh/ 25 | echo "${{ secrets.CONOHA_PRIVATE_KEY }}" > ~/.ssh/id_rsa 26 | chmod 600 ~/.ssh/id_rsa 27 | 28 | ssh -oStrictHostKeyChecking=no -i ~/.ssh/id_rsa ${{ secrets.CONOHA_USERNAME }}@${{ secrets.CONOHA_HOST }} -p ${{ secrets.CONOHA_PORT }} rm -rf public_html/mirumi.in/apps/how-far-map/* 29 | ls -lha dist/ 30 | scp -r -i ~/.ssh/id_rsa -P ${{ secrets.CONOHA_PORT }} dist/* ${{ secrets.CONOHA_USERNAME }}@${{ secrets.CONOHA_HOST }}:public_html/mirumi.in/apps/how-far-map/ 31 | -------------------------------------------------------------------------------- /src/views/DefaultMapView.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 37 | 38 | 40 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios" 2 | import App from "./App.vue" 3 | import router from "./router" 4 | import VueAxios from "vue-axios" 5 | import { createApp } from "vue" 6 | import { createI18n } from "vue-i18n" 7 | import { messages } from "@/utils/i18n" 8 | import { key, store } from "./store/index" 9 | import Toast, { PluginOptions, POSITION } from "vue-toastification" 10 | import "vue-toastification/dist/index.css" 11 | import "@/scss/toast.css" 12 | 13 | const toastOptions: PluginOptions = { 14 | closeButton: "button", 15 | closeOnClick: false, 16 | draggable: false, 17 | draggablePercent: 0.6, 18 | hideProgressBar: true, 19 | icon: false, 20 | maxToasts: 7, 21 | newestOnTop: true, 22 | pauseOnFocusLoss: false, 23 | pauseOnHover: true, 24 | position: POSITION.BOTTOM_LEFT, 25 | rtl: false, 26 | showCloseButtonOnHover: true, 27 | timeout: 4444, 28 | transition: "original_fade", 29 | } 30 | 31 | const i18n = createI18n({ 32 | legacy: false, // for Composition API 33 | locale: "ja", 34 | fallbackLocale: "en", 35 | messages, 36 | }) 37 | 38 | createApp(App) 39 | .use(i18n) 40 | .use(router) 41 | .use(store, key) 42 | .use(VueAxios, axios) 43 | .use(Toast, toastOptions) 44 | .mount("#app") 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "how-far-can-i-go", 3 | "version": "1.1.4", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve --mode dev", 7 | "build-dev": "vue-cli-service build --mode dev", 8 | "build-prd": "vue-cli-service build --mode prd", 9 | "test:unit": "vue-cli-service test:unit" 10 | }, 11 | "dependencies": { 12 | "@googlemaps/js-api-loader": "^1.13.1", 13 | "@types/google.maps": "^3.47.2", 14 | "@types/js-cookie": "^3.0.1", 15 | "@typescript-eslint/eslint-plugin": "^4.18.0", 16 | "@typescript-eslint/parser": "^4.18.0", 17 | "@vue/cli-plugin-babel": "^5.0.8", 18 | "@vue/cli-plugin-eslint": "^5.0.8", 19 | "@vue/cli-plugin-router": "^5.0.8", 20 | "@vue/cli-plugin-typescript": "^5.0.8", 21 | "@vue/cli-plugin-vuex": "^5.0.8", 22 | "@vue/cli-service": "^5.0.8", 23 | "@vue/compiler-sfc": "^3.0.0", 24 | "@vue/eslint-config-typescript": "^7.0.0", 25 | "axios": "^1.1.3", 26 | "core-js": "^3.6.5", 27 | "eslint": "^7.5.0", 28 | "eslint-plugin-vue": "^7.0.0", 29 | "js-cookie": "^3.0.1", 30 | "loaders.css": "^0.1.2", 31 | "mobile-detect": "^1.4.5", 32 | "sass": "^1.54.9", 33 | "sass-loader": "^8.0.2", 34 | "typescript": "^4.7.3", 35 | "vue": "^3.2.33", 36 | "vue-axios": "^3.5.1", 37 | "vue-i18n": "^9.2.0-beta.26", 38 | "vue-router": "^4.0.0-0", 39 | "vue-toastification": "^2.0.0-rc.5", 40 | "vuex": "^4.0.0-0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/utils/i18n.ts: -------------------------------------------------------------------------------- 1 | export const messages = { 2 | ja: { 3 | toast: { 4 | limit: "現在、1 分あたりのリクエスト数は10回に制限されています。", 5 | limit_all: "1 分あたりのリクエスト制限(利用ユーザー全体)に達しました。", 6 | map_limit: "1 ユーザーあたりの地図の読み込み回数には制限があります。また明日どうぞ。", 7 | geocode_limit: "1 ユーザーあたりのキーワード検索回数には制限があります。また明日どうぞ。", 8 | failedGetLocation: "現在地を取得できませんでした。", 9 | reason0: "原因は不明です。", 10 | reason1: "位置情報の取得が許可されていませんでした。", 11 | reason2: "正確な情報を取得できませんでした。", 12 | reason3: "タイムアウトエラーです。", 13 | notSupportGetLocation: "お使いのデバイスでは現在地の取得ができませんでした。", 14 | timemap_no_shell: "移動可能範囲が見つかりませんでした。", 15 | timemap_wrong_request: "地点指定が有効ではありません。現実的な陸地でしたか?", 16 | } 17 | }, 18 | en: { 19 | toast: { 20 | limit: "Currently, the number of requests per minute is limited to 10.", 21 | limit_all: "The request limit per minute (for all users) has been reached.", 22 | map_limit: "There is a limit to load the map per user. Please come back tomorrow.", 23 | geocode_limit: "There is a limit to serach with keywords per user. Please come back tomorrow.", 24 | failedGetLocation: "Failed to get current location.", 25 | reason0: "The cause is unknown.", 26 | reason1: "The cause is that location info acquisition was not allowed.", 27 | reason2: "The cause is due to signal conditions.", 28 | reason3: "This is a timeout error.", 29 | notSupportGetLocation: "Your device was not able to obtain information about your current location.", 30 | timemap_no_shell: "Movable range not found.", 31 | timemap_wrong_request: "The point you specified is not valid. Was it a normal land area?", 32 | } 33 | }, 34 | } 35 | -------------------------------------------------------------------------------- /src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | import { LatLng } from "./defines" 2 | 3 | export const round = ((value: number, base = 6): number => { 4 | return Math.round(value * (10**base)) / (10**base) 5 | }) 6 | 7 | export const shouldDarkMode = ((): boolean => { 8 | return (localStorage.getItem("theme") === "dark" || 9 | ((localStorage.getItem("theme") === "os_sync") || (!localStorage.getItem("theme"))) && window.matchMedia("(prefers-color-scheme: dark)").matches) 10 | }) 11 | 12 | export const getCountryDefaultPosition = ((): LatLng => { 13 | return { 14 | // Tokyo Sta. 15 | lat: 35.6809591, 16 | lng: 139.7673068, 17 | } 18 | }) 19 | 20 | export const delay = ((msec: number): Promise => { 21 | return new Promise((resolve) => { 22 | setTimeout(() => { 23 | resolve() 24 | }, msec) 25 | }) 26 | }) 27 | 28 | export const tabindexToID = ((tabindex: number, maxLength = 0): string => { 29 | let result = "" 30 | if (tabindex === 100 || tabindex === 101) { 31 | result = "walking" 32 | } else if (tabindex === 102) { 33 | result = "cycling" 34 | } else if (tabindex === 103 || tabindex === 104) { 35 | result = "driving" 36 | } else if (tabindex === 200 || tabindex === 201) { 37 | result = "min201" 38 | } else if (202 <= tabindex && tabindex <= 229) { 39 | result = "min" + tabindex.toString() 40 | } else if (tabindex === 230 || tabindex === 231) { 41 | result = "min230" 42 | } else if (tabindex === 300) { 43 | result = "search" 44 | } else if (301 <= tabindex && tabindex < 301 + maxLength) { 45 | result = "geo" + tabindex.toString() 46 | } else if (301 + maxLength <= tabindex) { 47 | result = "geo" + (maxLength - 1).toString() 48 | } 49 | return result 50 | }) 51 | -------------------------------------------------------------------------------- /src/scss/toast.css: -------------------------------------------------------------------------------- 1 | .Vue-Toastification__toast { 2 | display: flex; 3 | align-content: center; 4 | min-width: auto; 5 | min-height: 1em; 6 | padding: 0.8em 1.1em 0.8em 1.3em; 7 | border-radius: 5px; 8 | box-shadow: 2px 2px 3px 1px rgb(0 0 0 / 13%); 9 | } 10 | .Vue-Toastification__toast--info { 11 | color: #8C8E90; 12 | background-color: #F3F7F8; 13 | } 14 | .Vue-Toastification__toast--success { 15 | color: #479F63; 16 | background-color: #E3F4E9; 17 | } 18 | .Vue-Toastification__toast--warning { 19 | color: #B2B07F; 20 | background-color: #F4F4E6; 21 | } 22 | .Vue-Toastification__toast--error { 23 | color: #c07171; 24 | background-color: #F7EAED; 25 | } 26 | .Vue-Toastification__close-button { 27 | padding-left: 0.6em; 28 | } 29 | .Vue-Toastification__toast--info .Vue-Toastification__close-button { 30 | color: #A1A4A7; 31 | } 32 | .Vue-Toastification__toast--success .Vue-Toastification__close-button { 33 | color: #479F63; 34 | } 35 | .Vue-Toastification__toast--warning .Vue-Toastification__close-button { 36 | color: #B2B07F; 37 | } 38 | .Vue-Toastification__toast--error .Vue-Toastification__close-button { 39 | color: #CA8787; 40 | } 41 | @keyframes fadeInUp { 42 | from { 43 | opacity: 0.5; 44 | transform: translateY(100%); 45 | } 46 | to { 47 | opacity: 1; 48 | transform: none; 49 | } 50 | } 51 | @keyframes fadeOutDown { 52 | from { 53 | opacity: 1; 54 | } 55 | to { 56 | opacity: 0; 57 | transform: translateY(100%); 58 | } 59 | } 60 | .original_fade-enter-active { 61 | animation: fadeInUp 111ms ease both; 62 | } 63 | .original_fade-leave-active { 64 | animation: fadeOutDown 133ms ease-in-out both; 65 | } 66 | .original_fade-move { 67 | transition: all 333ms ease; 68 | } 69 | -------------------------------------------------------------------------------- /src/utils/darkStyle.ts: -------------------------------------------------------------------------------- 1 | export const darkStyle = [ 2 | { elementType: "geometry", stylers: [{ color: "#242b40" }] }, 3 | { elementType: "labels.text.stroke", stylers: [{ color: "#242b40" }] }, 4 | { elementType: "labels.text.fill", stylers: [{ color: "#746855" }] }, 5 | { 6 | featureType: "administrative.locality", 7 | elementType: "labels.text.fill", 8 | stylers: [{ color: "#b87a49" }], 9 | }, 10 | { 11 | featureType: "poi", 12 | elementType: "labels.text.fill", 13 | stylers: [{ color: "#b87a49" }], 14 | }, 15 | { 16 | featureType: "poi.park", 17 | elementType: "geometry", 18 | stylers: [{ color: "#263c3f" }], 19 | }, 20 | { 21 | featureType: "poi.park", 22 | elementType: "labels.text.fill", 23 | stylers: [{ color: "#6b9a76" }], 24 | }, 25 | { 26 | featureType: "road", 27 | elementType: "geometry", 28 | stylers: [{ color: "#38414e" }], 29 | }, 30 | { 31 | featureType: "road", 32 | elementType: "geometry.stroke", 33 | stylers: [{ color: "#212a37" }], 34 | }, 35 | { 36 | featureType: "road", 37 | elementType: "labels.text.fill", 38 | stylers: [{ color: "#9ca5b3" }], 39 | }, 40 | { 41 | featureType: "road.highway", 42 | elementType: "geometry", 43 | stylers: [{ color: "#746855" }], 44 | }, 45 | { 46 | featureType: "road.highway", 47 | elementType: "geometry.stroke", 48 | stylers: [{ color: "#1f2835" }], 49 | }, 50 | { 51 | featureType: "road.highway", 52 | elementType: "labels.text.fill", 53 | stylers: [{ color: "#f3d19c" }], 54 | }, 55 | { 56 | featureType: "transit", 57 | elementType: "geometry", 58 | stylers: [{ color: "#2f3948" }], 59 | }, 60 | { 61 | featureType: "transit.station", 62 | elementType: "labels.text.fill", 63 | stylers: [{ color: "#b87a49" }], 64 | }, 65 | { 66 | featureType: "water", 67 | elementType: "geometry", 68 | stylers: [{ color: "#17263c" }], 69 | }, 70 | { 71 | featureType: "water", 72 | elementType: "labels.text.fill", 73 | stylers: [{ color: "#515c6d" }], 74 | }, 75 | { 76 | featureType: "water", 77 | elementType: "labels.text.stroke", 78 | stylers: [{ color: "#17263c" }], 79 | }, 80 | ] 81 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | How far can I go? | Walking Distance Map 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 37 | 38 | 39 | 42 |
43 | 44 | 45 | -------------------------------------------------------------------------------- /src/scss/_css.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * vars 3 | */ 4 | $text: #4e4e4e; 5 | $text_white: #f2f2f2; 6 | $text_white_hover: #e3e3e3; 7 | $primary: #ec6839; 8 | $primary_hover: #e26234; 9 | 10 | /** 11 | * globals 12 | */ 13 | * { 14 | box-sizing: border-box; 15 | } 16 | html, body { 17 | margin: 0; 18 | padding: 0; 19 | height: 100%; 20 | overflow: hidden; 21 | } 22 | a { 23 | color: $text; 24 | } 25 | ul, li { 26 | margin: 0; 27 | padding: 0; 28 | list-style: none; 29 | } 30 | .button { 31 | display: inline-block; 32 | padding: 0.37rem 1.1rem 0.375rem; 33 | font-size: 1rem; 34 | font-weight: 500; 35 | line-height: 1.5; 36 | text-align: center; 37 | vertical-align: middle; 38 | text-decoration: none; 39 | border: 2.3px solid transparent; 40 | border-radius: 4.4px; 41 | background-color: transparent; 42 | transition: all 0.07s ease-in; 43 | cursor: pointer; 44 | user-select: none; 45 | &.fill { 46 | color: $text_white; 47 | border-color: $primary; 48 | background-color: $primary; 49 | &:hover { 50 | color: text_white_hover; 51 | border-color: $primary_hover; 52 | background-color: $primary_hover; 53 | } 54 | } 55 | &.outline { 56 | color: $primary; 57 | border-color: $primary; 58 | &:hover { 59 | opacity: 0.777; 60 | } 61 | } 62 | } 63 | .input { 64 | transition: border-color 0.13s ease-in-out, box-shadow 0.13s ease-in-out; 65 | } 66 | .input:focus { 67 | border-color: #feae86 !important; 68 | outline: 0; 69 | box-shadow: 0 0 0 0.25rem rgba(242, 101, 11, 0.37) !important; 70 | } 71 | .input::placeholder { 72 | color: #d2d2d2; 73 | } 74 | ::-webkit-scrollbar { 75 | width: 15px; 76 | } 77 | ::-webkit-scrollbar-thumb { 78 | border: 4px solid rgba(0, 0, 0, 0); 79 | background-clip: padding-box; 80 | border-radius: 9999px; 81 | background-color: #d6d1c9; 82 | } 83 | 84 | /** 85 | * vue transition 86 | */ 87 | .fade-enter-active, 88 | .fade-leave-active { 89 | transition: opacity 0.13s ease-out; 90 | } 91 | .fade-enter-from, 92 | .fade-leave-to { 93 | opacity: 0; 94 | } 95 | .fade-enter-to, 96 | .fade-leave-from { 97 | opacity: 0.666; 98 | } 99 | .slide-enter-active, 100 | .slide-leave-active { 101 | transition: transform 0.13s ease-out; 102 | } 103 | .slide-enter-from, 104 | .slide-leave-to { 105 | transform: translate(100vw, 0px); 106 | } 107 | .slide-enter-to, 108 | .slide-leave-from { 109 | transform: translate(0px, 0px); 110 | } 111 | 112 | /** 113 | * responsive 114 | */ 115 | @mixin mobile { 116 | @media screen and (max-width: 768px) { 117 | @content; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/components/modules/ModalLink.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 35 | 36 | 78 | -------------------------------------------------------------------------------- /src/components/parts/SvgIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 66 | 67 | 72 | -------------------------------------------------------------------------------- /src/views/MapView.vue: -------------------------------------------------------------------------------- 1 | 143 | 144 | 838 | 839 | 1351 | 1359 | --------------------------------------------------------------------------------