<template> <div class="actors"> <nav ref="filter" class="filter" > <ul class="genders nolist"> <li class="gender"> <router-link :to="{ name: 'actors', params: { gender: 'all', letter, pageNumber: 1 } }" :class="{ selected: gender === 'all' }" class="gender-link all" >all</router-link> </li> <li class="gender"> <router-link :to="{ name: 'actors', params: { gender: 'female', letter, pageNumber: 1 } }" :class="{ selected: gender === 'female' }" class="gender-link female" ><Gender gender="female" /></router-link> </li> <li class="gender"> <router-link :to="{ name: 'actors', params: { gender: 'male', letter, pageNumber: 1 } }" :class="{ selected: gender === 'male' }" class="gender-link male" replace ><Gender gender="male" /></router-link> </li> <li class="gender"> <router-link :to="{ name: 'actors', params: { gender: 'trans', letter, pageNumber: 1 } }" :class="{ selected: gender === 'trans' }" class="gender-link transsexual" replace ><Gender gender="transsexual" /></router-link> </li> <li class="gender"> <router-link :to="{ name: 'actors', params: { gender: 'other', letter, pageNumber: 1 } }" :class="{ selected: gender === 'other' }" class="gender-link other" replace ><Icon icon="question5" /></router-link> </li> </ul> <ul class="letters nolist"> <li v-for="letterX in letters" :key="letterX" class="letter" > <router-link :to="{ name: 'actors', params: { gender, letter: letterX, pageNumber: 1 } }" :class="{ selected: letterX === letter }" class="letter-link" replace >{{ letterX || 'All' }}</router-link> </li> </ul> </nav> <nav class="filter"> <ul class="nolist"> <li> <Tooltip class="filter boobs"> <span class="filter-trigger" :class="{ enabled: ageRequired }" ><Icon icon="vcard" />Age</span> <template v-slot:tooltip> <RangeFilter label="age" :min="18" :max="100" :value="age" :disabled="!ageRequired" @enable="(checked) => updateValue('ageRequired', checked, true)" @input="(range) => updateValue('age', range, false)" @change="(range) => updateValue('age', range, true)" > <template v-slot:start><Icon icon="leaf" /></template> <template v-slot:end><Icon icon="tree3" /></template> </RangeFilter> <div class="filter-section"> <label class="filter-label"> <span class="label"> <Checkbox :checked="dobRequired" class="checkbox" @change="(checked) => updateValue('dobRequired', checked, true)" />Date of birth </span> </label> <div class="input-container" @click="() => updateValue('dobRequired', true, true)" > <input v-model="dob" :disabled="!dobRequired" type="date" class="input" @change="updateFilters" > </div> </div> </template> </Tooltip> </li> <li> <Tooltip class="filter"> <span class="filter-trigger boobs" :class="{ enabled: boobSizeRequired || naturalBoobs !== 1 }" ><Icon icon="boobs" />Boobs</span> <template v-slot:tooltip> <RangeFilter label="size" :min="0" :max="boobSizes.length - 1" :value="boobSize" :values="boobSizes" :disabled="!boobSizeRequired" @enable="(checked) => updateValue('boobSizeRequired', checked, true)" @input="(range) => updateValue('boobSize', range, false)" @change="(range) => updateValue('boobSize', range, true)" > <template v-slot:start><Icon icon="boobs-small" /></template> <template v-slot:end><Icon icon="boobs-big" /></template> </RangeFilter> <div class="filter-section"> <span class="filter-label">Enhanced</span> <span :class="{ [['off', 'default', 'on'][naturalBoobs]]: true }" class="toggle-container noclick" > <span class="toggle-label off" @click="updateValue('naturalBoobs', 0)" ><Icon icon="leaf" /></span> <input v-model.number="naturalBoobs" class="toggle" type="range" min="0" max="2" @change="updateFilters" > <span class="toggle-label on" @click="updateValue('naturalBoobs', 2)" ><Icon icon="magic-wand2" /></span> </span> </div> </template> </Tooltip> </li> <li> <Tooltip class="filter boobs"> <span class="filter-trigger" :class="{ enabled: heightRequired || weightRequired }" ><Icon icon="rulers" />Physique</span> <template v-slot:tooltip> <RangeFilter label="height" :min="50" :max="220" :value="height" :disabled="!heightRequired" unit="cm" @enable="(checked) => updateValue('heightRequired', checked, true)" @input="(range) => updateValue('height', range, false)" @change="(range) => updateValue('height', range, true)" > <template v-slot:start><Icon icon="height-short" /></template> <template v-slot:end><Icon icon="height" /></template> </RangeFilter> <RangeFilter label="weight" :min="30" :max="200" :value="weight" :disabled="!weightRequired" unit="kg" @enable="(checked) => updateValue('weightRequired', checked, true)" @input="(range) => updateValue('weight', range, false)" @change="(range) => updateValue('weight', range, true)" > <template v-slot:start><Icon icon="meter-slow" /></template> <template v-slot:end><Icon icon="meter-fast" /></template> </RangeFilter> </template> </Tooltip> </li> </ul> </nav> <div class="tiles"> <Actor v-for="actor in actors" :key="`actor-${actor.id}`" :actor="actor" /> </div> <Pagination v-if="totalCount > 0" :items-total="totalCount" :items-per-page="limit" class="pagination-bottom" /> <Footer /> </div> </template> <script> import dayjs from 'dayjs'; import Actor from './tile.vue'; import Gender from './gender.vue'; import Checkbox from '../form/checkbox.vue'; import RangeFilter from './filter-range.vue'; import Pagination from '../pagination/pagination.vue'; const toggleValues = [true, null, false]; const boobSizes = 'ABCDEFGHZ'.split(''); function updateFilters() { this.$router.push({ name: 'actors', params: { pageNumber: 1, gender: this.gender, letter: this.letter, }, query: { nb: this.naturalBoobs !== 1 ? this.naturalBoobs : undefined, bs: this.boobSizeRequired ? this.boobSize.join(',') : undefined, h: this.heightRequired ? this.height.join(',') : undefined, w: this.weightRequired ? this.weight.join(',') : undefined, age: this.ageRequired ? this.age.join(',') : undefined, dob: this.dobRequired ? this.dob : undefined, }, }); } function updateValue(prop, value, load = true) { this[prop] = value; if (load) { this.updateFilters(); } } async function fetchActors(scroll) { const curatedGender = this.gender.replace('trans', 'transsexual'); const { actors, totalCount } = await this.$store.dispatch('fetchActors', { limit: this.limit, pageNumber: Number(this.$route.params.pageNumber) || 1, letter: this.letter.replace('all', ''), gender: curatedGender === 'other' ? null : curatedGender, age: this.ageRequired && this.age, dob: this.dobRequired && this.dob, boobSize: this.boobSizeRequired && this.boobSize, naturalBoobs: toggleValues[this.naturalBoobs] ?? null, height: this.heightRequired && this.height, weight: this.weightRequired && this.weight, }); this.actors = actors; this.totalCount = totalCount; if (scroll) { this.$refs.filter?.scrollIntoView(); } } function letter() { return this.$route.params.letter || 'all'; } function gender() { return this.$route.params.gender || 'all'; } async function route(to, from) { const scroll = to.params.pageNumber !== from.params.pageNumber || to.params.gender !== from.params.gender || to.params.letter !== from.params.letter; await this.fetchActors(scroll); } async function mounted() { this.pageTitle = 'Actors'; await this.fetchActors(); } export default { components: { Actor, Checkbox, Gender, RangeFilter, Pagination, }, data() { return { actors: [], pageTitle: null, totalCount: 0, limit: 50, letters: ['all'].concat(Array.from({ length: 26 }, (value, index) => String.fromCharCode(index + 97).toUpperCase())), age: this.$route.query.age?.split(',') || [18, 100], ageRequired: !!this.$route.query.age, dob: this.$route.query.dob || dayjs().subtract(21, 'years').format('YYYY-MM-DD'), dobRequired: !!this.$route.query.dob, boobSizes, boobSize: this.$route.query.bs?.split(',') || ['A', 'Z'], boobSizeRequired: !!this.$route.query.bs, naturalBoobs: Number(this.$route.query.nb) || 1, height: this.$route.query.h?.split(',').map(Number) || [50, 220], heightRequired: !!this.$route.query.h, weight: this.$route.query.w?.split(',').map(Number) || [30, 200], weightRequired: !!this.$route.query.w, }; }, computed: { letter, gender, }, watch: { $route: route, }, mounted, methods: { fetchActors, updateFilters, updateValue, }, }; </script> <style lang="scss"> .gender-link { &.selected .gender .icon { fill: var(--text-light); filter: none; } &:hover:not(.selected) { .gender .icon { fill: var(--text-light); } .male .icon { filter: drop-shadow(0 0 1px var(--male)); } .female .icon { filter: drop-shadow(0 0 1px var(--female)); } } &:hover:not(.selected) .transsexual .icon { fill: var(--female); filter: drop-shadow(1px 0 0 var(--text-light)) drop-shadow(-1px 0 0 var(--text-light)) drop-shadow(0 1px 0 var(--text-light)) drop-shadow(0 -1px 0 var(--text-light)) drop-shadow(1px 0 0 var(--male)) drop-shadow(-1px 0 0 var(--male)) drop-shadow(0 1px 0 var(--male)) drop-shadow(0 -1px 0 var(--male)) drop-shadow(0 0 1px rgba(0, 0, 0, 0.5)); } } </style> <style lang="scss" scoped> @import 'breakpoints'; .actors { display: flex; flex-direction: column; } .tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr)); grid-gap: .5rem; padding: 1rem; flex-grow: 1; } .filter { display: flex; justify-content: center; align-items: center; padding: 0 1rem; margin: 0 0 1rem 0; &:first-child { margin: 1rem 0; } } .genders { display: flex; flex-shrink: 0; padding: 0 .5rem 0 0; border-right: solid 1px var(--shadow-hint); margin: 0 1rem 0 0; } .letter, .gender { display: inline-block; } .letter-link, .gender-link { width: 2.5rem; height: 2.5rem; display: flex; align-items: center; justify-content: center; box-sizing: border-box; margin: .25rem .5rem .25rem 0; color: var(--shadow); background: var(--background); font-weight: bold; text-decoration: none; box-shadow: 0 0 3px var(--darken-weak); .male, .female, .transsexual { padding: .2rem 0 0 0; } .icon { fill: var(--shadow); } &:hover { color: var(--text); cursor: pointer; .icon { fill: var(--text); } } &.selected { background: var(--primary); color: var(--text-light); &.other .icon { fill: var(--text-light); } } } .filter-trigger { display: inline-flex; align-items: center; color: var(--shadow); font-weight: bold; .icon { fill: var(--shadow); width: 1rem; height: 1rem; margin: -.1rem .75rem 0 0; } &:hover { color: var(--shadow-strong); cursor: pointer; .icon { fill: var(--shadow-strong); } } &.enabled { color: var(--primary); .icon { fill: var(--primary); } } } .label-values { font-weight: normal; } .filter-split { display: flex; align-items: center; } .filter-label { display: flex; justify-content: space-between; padding: .75rem .5rem .5rem .5rem; color: var(--shadow); font-weight: bold; font-size: .9rem; .checkbox { margin: 0 .75rem 0 0; } .label { display: inline-flex; align-items: center; text-transform: capitalize; } } .input-container { box-sizing: border-box; padding: 0 .5rem .5rem .5rem; .input { width: 100%; } } .toggle-container, .range-container { display: flex; flex-grow: 1; align-items: center; padding: .5rem 0; &.on { .toggle-label.on { color: var(--enabled); .icon { fill: var(--enabled); } } .toggle { background-color: var(--enabled-background); &::-webkit-slider-thumb { background: var(--enabled); } &::-moz-range-thumb { background: var(--enabled); } } } &.off { .toggle-label.off { color: var(--disabled); .icon { fill: var(--disabled); } } .toggle { background-color: var(--disabled-background); &::-webkit-slider-thumb { background: var(--disabled); } &::-moz-range-thumb { background: var(--disabled); } } } } .toggle-label { display: inline-flex; justify-content: center; min-width: 1.5rem; flex-shrink: 0; padding: 0 .5rem; color: var(--shadow); font-weight: bold; font-size: .9rem; &.on { text-align: right; } .icon { fill: var(--shadow); } &:hover { cursor: pointer; &.on { color: var(--enabled); .icon { fill: var(--enabled); } } &.off { color: var(--disabled); .icon { fill: var(--disabled); } } } } .toggle { width: 0; flex-grow: 1; height: 1.25rem; appearance: none; border-radius: 1rem; background-color: var(--shadow-hint); background-image: radial-gradient(circle, var(--shadow-weak) .3rem, transparent calc(.3rem + 1px)); cursor: pointer; &::-webkit-slider-thumb { appearance: none; background: var(--disabled-handle); width: 1.25rem; height: 1.25rem; border-radius: .625rem; box-shadow: 0 0 3px var(--darken-weak); } &::-moz-range-thumb { appearance: none; background: var(--disabled-handle); width: 1.25rem; height: 1.25rem; border: none; border-radius: .625rem; box-shadow: 0 0 3px var(--darken-weak); } } @media(max-width: $breakpoint) { .genders { flex-direction: column; } } @media(max-width: $breakpoint-micro) { .tiles { grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr)); } } </style>