1716 Commits

Author SHA1 Message Date
DebaucheryLibrarian
33a327a04b Merge branch 'master' into experimental 2022-03-30 23:00:29 +02:00
DebaucheryLibrarian
a46061e247 1.212.9 2022-03-30 16:11:09 +02:00
DebaucheryLibrarian
94e07ff23d Added Bang! Podcast channel. 2022-03-30 16:11:07 +02:00
DebaucheryLibrarian
4811befcf6 1.212.8 2022-03-30 15:45:51 +02:00
DebaucheryLibrarian
c455f02c66 Updated Men URLs. 2022-03-30 15:45:41 +02:00
DebaucheryLibrarian
efc5620a28 1.212.7 2022-03-30 01:17:56 +02:00
DebaucheryLibrarian
61123fdb6a Added Accept-Language header to MindGeek requests, seems to help with acquiring sessions. 2022-03-30 01:17:54 +02:00
DebaucheryLibrarian
3ec6911d46 1.212.6 2022-03-29 23:24:58 +02:00
DebaucheryLibrarian
2021093645 Marked Dane Jones and Lesbea as native sites. 2022-03-29 23:24:55 +02:00
DebaucheryLibrarian
1c72dc202f 1.212.5 2022-03-28 23:44:44 +02:00
DebaucheryLibrarian
1ef946fa77 Marked Mile High sites as native. 2022-03-28 23:44:42 +02:00
DebaucheryLibrarian
3b6bbc39ff 1.212.4 2022-03-28 20:05:28 +02:00
DebaucheryLibrarian
481c9feada Fixed missing scenes photos breaking album. 2022-03-28 20:05:25 +02:00
DebaucheryLibrarian
953b3e9568 1.212.3 2022-03-28 00:36:05 +02:00
DebaucheryLibrarian
bdd2e68f49 Fixed centering logic in banner. 2022-03-28 00:36:04 +02:00
DebaucheryLibrarian
e4cc349302 1.212.2 2022-03-28 00:32:00 +02:00
DebaucheryLibrarian
6547b93e55 Fixed broken scene photo length check in release banner. 2022-03-28 00:31:59 +02:00
DebaucheryLibrarian
bb9649d23b 1.212.1 2022-03-28 00:22:59 +02:00
DebaucheryLibrarian
9e2eaef9d1 Added dedicated serie photos table, renamed serie scene photo function. Fixed covers and scene photos in banner and album. 2022-03-28 00:22:57 +02:00
DebaucheryLibrarian
1c3ee75d3b 1.212.0 2022-03-27 23:42:06 +02:00
DebaucheryLibrarian
15c9af8057 Added dedicated movie photo table, renamed scene photo function. 2022-03-27 23:42:03 +02:00
DebaucheryLibrarian
295573c1ef 1.211.2 2022-03-27 00:27:29 +01:00
DebaucheryLibrarian
e93e8ace5c Added deep scene force parameter to MindGeek scraper. 2022-03-27 00:27:26 +01:00
DebaucheryLibrarian
43af7ba777 1.211.1 2022-03-26 17:56:24 +01:00
DebaucheryLibrarian
0dad5b0d68 Added series removal utils. 2022-03-26 17:56:22 +01:00
DebaucheryLibrarian
ae9b793318 1.211.0 2022-03-26 17:40:23 +01:00
DebaucheryLibrarian
fd8170f223 Added series. 2022-03-26 17:40:20 +01:00
DebaucheryLibrarian
661b8b716b 1.210.1 2022-03-09 23:26:50 +01:00
DebaucheryLibrarian
5ff076cac3 Added DP Star Sex Challenges to Digital Playground. 2022-03-09 23:26:48 +01:00
DebaucheryLibrarian
41c100ac4e 1.210.0 2022-03-04 23:32:28 +01:00
DebaucheryLibrarian
c6e977f842 Added movie support to MindGeek scraper. 2022-03-04 23:32:09 +01:00
DebaucheryLibrarian
50b7f521b5 1.209.5 2022-02-27 21:15:07 +01:00
DebaucheryLibrarian
f0d0ee3acc Removed redundant scenes path from Digital Playground main channel. 2022-02-27 21:15:05 +01:00
DebaucheryLibrarian
7b3bdadd44 1.209.4 2022-02-25 22:13:43 +01:00
DebaucheryLibrarian
5deba6b90f Passing resolved parameters into before-scrape methods. 2022-02-25 22:13:41 +01:00
DebaucheryLibrarian
a5afffc968 1.209.3 2022-02-25 00:00:21 +01:00
DebaucheryLibrarian
a239a5c593 Fixed entity scenes function for studios. Added triple anal tag alias. 2022-02-25 00:00:18 +01:00
DebaucheryLibrarian
e56e7333e3 1.209.2 2022-02-24 23:33:26 +01:00
DebaucheryLibrarian
d55e3c37cd Updated media seeds from LegalPorno to AnalVids. 2022-02-24 23:33:19 +01:00
DebaucheryLibrarian
97b78ea016 1.209.1 2022-02-24 23:30:49 +01:00
DebaucheryLibrarian
3e290b74dc Updated affiliates from LegalPorno to AnalVids. 2022-02-24 23:30:47 +01:00
DebaucheryLibrarian
65141207ae 1.209.0 2022-02-24 23:28:27 +01:00
DebaucheryLibrarian
17dfeac1af Renamed LegalPorno to AnalVids, added missing studios. 2022-02-24 23:28:24 +01:00
DebaucheryLibrarian
4a9c428d69 1.208.1 2022-02-24 22:43:01 +01:00
DebaucheryLibrarian
333f252099 Added BJ Raw to Radical. 2022-02-24 22:42:59 +01:00
DebaucheryLibrarian
38232f258a 1.208.0 2022-02-24 22:33:03 +01:00
DebaucheryLibrarian
582269cfaa Added Radical metadata layout scraper and the Got Filled and Inserted channels. 2022-02-24 22:33:00 +01:00
DebaucheryLibrarian
0b646429fd 1.207.5 2022-02-20 23:40:23 +01:00
DebaucheryLibrarian
fecef6c1cf Added missing MOFOS serie channels. 2022-02-20 23:40:20 +01:00
DebaucheryLibrarian
73e5404c44 1.207.4 2022-02-20 16:52:32 +01:00
DebaucheryLibrarian
690d2bb3ed Added MOFOS to default rate limit config, added light logos. 2022-02-20 16:52:29 +01:00
DebaucheryLibrarian
1dd935e1e9 1.207.3 2022-02-14 01:27:58 +01:00
DebaucheryLibrarian
8386230f33 Added missing Team Skeet sites. 2022-02-14 01:27:56 +01:00
DebaucheryLibrarian
5a68b06137 1.207.2 2022-02-13 01:34:41 +01:00
DebaucheryLibrarian
5918364cf5 Preventing unnecessary deep scrapes in Team Skeet scraper. 2022-02-13 01:34:39 +01:00
DebaucheryLibrarian
df4d860d35 1.207.1 2022-02-12 17:19:49 +01:00
DebaucheryLibrarian
d9f0db6e3c Fixed reading screen ID from non-existent item in Bang scraper. 2022-02-12 17:19:47 +01:00
DebaucheryLibrarian
7372b560b2 1.207.0 2022-02-12 17:16:26 +01:00
DebaucheryLibrarian
9d638c685c 1.206.12 2022-02-12 17:16:23 +01:00
DebaucheryLibrarian
5da1acc38d Added media resize. 2022-02-12 17:16:20 +01:00
DebaucheryLibrarian
c7c62e220d Removed console log from Bang scraper. 2022-02-12 16:22:41 +01:00
DebaucheryLibrarian
9edd652a2c 1.206.11 2022-02-12 16:22:03 +01:00
DebaucheryLibrarian
cde760c1ea Preventing Bang! scraper from redundant deep scraping when base release is available. 2022-02-12 16:22:00 +01:00
DebaucheryLibrarian
19c7f958e1 1.206.10 2022-02-12 03:26:43 +01:00
DebaucheryLibrarian
c7710afdbf Fixed Bang! scraped failing when scene has no photos. 2022-02-12 03:26:40 +01:00
DebaucheryLibrarian
ba18a3dadb 1.206.9 2022-02-11 22:14:46 +01:00
DebaucheryLibrarian
496c29e569 Configured Reality Kings to fetch session from RK scene overview. 2022-02-11 22:14:44 +01:00
DebaucheryLibrarian
98a72a4929 1.206.8 2022-02-09 00:19:56 +01:00
DebaucheryLibrarian
c4f0b48932 Fixed cover dimensions on movie tile to show lazy image, showing missing cover icon. 2022-02-09 00:19:54 +01:00
DebaucheryLibrarian
93abbab873 1.206.7 2022-02-08 23:59:48 +01:00
DebaucheryLibrarian
f7bbab08bd Added Porn Pros main channel and working URLs for Flexible Positions, Public Violations and Squirt Disgrace. Added bisexual tag photos. 2022-02-08 23:59:44 +01:00
DebaucheryLibrarian
1e4ddf2bbf 1.206.6 2022-02-08 00:19:36 +01:00
DebaucheryLibrarian
9b7879bff7 Removed channel ID from Gamma update query to prevent duplicate scenes. Added sexuality tags with covers to overview. 2022-02-08 00:19:34 +01:00
DebaucheryLibrarian
03d96d4dec 1.206.5 2022-02-07 22:29:11 +01:00
DebaucheryLibrarian
1dfa034332 Handling 'page not found' with 200 OK in Gamma scraper. 2022-02-07 22:29:09 +01:00
DebaucheryLibrarian
196a1d33e3 1.206.4 2022-02-07 22:16:46 +01:00
DebaucheryLibrarian
3d349c413f Fixed Adult Time scraper. 2022-02-07 22:16:43 +01:00
DebaucheryLibrarian
bd5babd37e 1.206.3 2022-02-07 21:58:31 +01:00
DebaucheryLibrarian
fffe9080f3 Added stand-alone Spizoo sites. 2022-02-07 21:58:27 +01:00
DebaucheryLibrarian
5630b16e99 1.206.2 2022-02-07 01:57:14 +01:00
DebaucheryLibrarian
4bcf7ef45b Storing associations if existing media are found. 2022-02-07 01:57:09 +01:00
DebaucheryLibrarian
9abc1d48ac 1.206.1 2022-02-05 17:14:37 +01:00
DebaucheryLibrarian
ceaf5a3217 Not storing shallow movie data when argument is disabled. 2022-02-05 17:14:34 +01:00
DebaucheryLibrarian
36d84fb98b 1.206.0 2022-02-03 00:04:51 +01:00
DebaucheryLibrarian
3d80e9d77b Added movie URL file support. Fixed Gamma movie API scraper movie URL. 2022-02-03 00:04:48 +01:00
DebaucheryLibrarian
1dc38ffacc 1.205.20 2022-02-02 23:53:44 +01:00
DebaucheryLibrarian
ee210b5c68 Replaced Gender X with Gender X Films, fixed Zero Tolerance movie URLs. 2022-02-02 23:53:42 +01:00
DebaucheryLibrarian
3aae3cd3a1 1.205.19 2022-02-02 23:51:20 +01:00
DebaucheryLibrarian
f1a7f2f905 Added Gamma movie API scraper. 2022-02-02 23:51:15 +01:00
DebaucheryLibrarian
0b7e0319f1 1.205.18 2022-02-02 22:25:06 +01:00
DebaucheryLibrarian
10b8abf706 Updated Zero Tolerance channel to Zero Tolerance Films. 2022-02-02 22:25:04 +01:00
DebaucheryLibrarian
b163223ff1 1.205.17 2022-02-01 21:18:58 +01:00
DebaucheryLibrarian
b9f3eb85f9 Ignoring Gamma master categories in update scrape as these seem to contain irrelevant tags. 2022-02-01 21:18:54 +01:00
DebaucheryLibrarian
285a65f018 1.205.16 2022-01-30 22:15:09 +01:00
DebaucheryLibrarian
815d56d334 Preventing media associations when use-reference is unavailable. 2022-01-30 22:15:07 +01:00
DebaucheryLibrarian
27a9a233e2 1.205.15 2022-01-30 17:05:20 +01:00
DebaucheryLibrarian
22864105ac Writing media associations to disk for debugging. 2022-01-30 17:05:18 +01:00
DebaucheryLibrarian
7cf47cbf8d 1.205.14 2022-01-30 00:18:17 +01:00
DebaucheryLibrarian
502c588958 Updated Bang Bros product ID for Haze Her, removed wrong ones. 2022-01-30 00:18:15 +01:00
DebaucheryLibrarian
81792a7c2f 1.205.13 2022-01-30 00:08:41 +01:00
DebaucheryLibrarian
784d326584 Updated Bang Bros product ID for College Rules. 2022-01-30 00:08:39 +01:00
DebaucheryLibrarian
83dc7aa578 1.205.12 2022-01-29 23:50:01 +01:00
DebaucheryLibrarian
5a3b27fbf7 Fixed movie cover index for overview in GraphQL query. 2022-01-29 23:49:59 +01:00
DebaucheryLibrarian
5ff83a9497 1.205.11 2022-01-29 23:39:13 +01:00
DebaucheryLibrarian
07cb39c756 Fixed movie cover index in GraphQL query. 2022-01-29 23:39:12 +01:00
DebaucheryLibrarian
fffd54995d 1.205.10 2022-01-29 17:13:13 +01:00
DebaucheryLibrarian
5302176f0b Re-added channel count to networks page. 2022-01-29 17:13:09 +01:00
DebaucheryLibrarian
efd7cf8acc 1.205.9 2022-01-29 16:38:42 +01:00
DebaucheryLibrarian
52218c30f8 Not fetching mobile Gamma page when photos are not expected. 2022-01-29 16:38:36 +01:00
DebaucheryLibrarian
04c176fa8b 1.205.8 2022-01-29 03:34:31 +01:00
DebaucheryLibrarian
af320684b4 Added Bang Bros sites. 2022-01-29 03:34:26 +01:00
DebaucheryLibrarian
b2ba14ce45 1.205.7 2022-01-29 02:39:17 +01:00
DebaucheryLibrarian
d776cc2f71 Added Abuse Me to Bang Bros network. 2022-01-29 02:39:14 +01:00
DebaucheryLibrarian
b4bed2f623 1.205.6 2022-01-29 01:21:45 +01:00
DebaucheryLibrarian
5c5a11fdca Added Bang Bros members page scraper (not for full scenes). 2022-01-29 01:21:41 +01:00
DebaucheryLibrarian
a22c62d893 1.205.5 2022-01-28 04:05:38 +01:00
DebaucheryLibrarian
90bc2f3724 Changed release media association existence check, added log for details. 2022-01-28 04:05:29 +01:00
DebaucheryLibrarian
8cdc794a3a 1.205.4 2022-01-26 13:52:41 +01:00
DebaucheryLibrarian
250618df8b Added entity scene count function. 2022-01-26 13:52:39 +01:00
DebaucheryLibrarian
b62ae00c54 1.205.3 2022-01-26 01:47:36 +01:00
DebaucheryLibrarian
67d0a9e0e0 Fixed scene entity tag association. 2022-01-26 01:47:34 +01:00
b4425bc3bb Merge pull request 'Add Facials4k' (#44) from boiii/traxxx:add-facials-4k into master
Reviewed-on: http://gitea.unknown.name/DebaucheryLibrarian/traxxx/pulls/44
2022-01-25 22:29:10 +00:00
d77c145de3 Update 'seeds/02_sites.js' 2022-01-25 20:56:11 +00:00
DebaucheryLibrarian
6753f923d9 1.205.2 2022-01-25 15:34:17 +01:00
DebaucheryLibrarian
452f725332 Switched poster URL preference in Gamma scraper to prevent cropped image on XEmpire. 2022-01-25 15:34:15 +01:00
DebaucheryLibrarian
7c1f45bcfb 1.205.1 2022-01-24 22:29:29 +01:00
DebaucheryLibrarian
11ea54f6e8 Fixed outdated alerts query. 2022-01-24 22:29:27 +01:00
DebaucheryLibrarian
5b623ee46f 1.205.0 2022-01-24 00:15:19 +01:00
DebaucheryLibrarian
7ceec1c739 Added indexes to scene tag and movie scene tables improve performance. Removed scene count from channel overview, too expensive. 2022-01-24 00:15:11 +01:00
DebaucheryLibrarian
fc318536aa 1.204.13 2022-01-23 02:46:44 +01:00
DebaucheryLibrarian
cd8f690ac6 Preferring network over channel in deep URL channel matching. 2022-01-23 02:46:42 +01:00
DebaucheryLibrarian
240a8e86fd 1.204.12 2022-01-22 23:20:10 +01:00
DebaucheryLibrarian
c557f0f1f0 Improved Gamma image sources to prevent cropped images. 2022-01-22 23:20:08 +01:00
DebaucheryLibrarian
86f56695e2 1.204.11 2022-01-20 00:54:13 +01:00
DebaucheryLibrarian
20da2d1cf6 Reusing batch ID for movies to preserve new-flag. 2022-01-20 00:54:10 +01:00
DebaucheryLibrarian
372db86927 Disabled MindGeek session bundling to analyze Too Many Requests errors. 2022-01-16 22:24:47 +01:00
DebaucheryLibrarian
4c503a3177 1.204.10 2022-01-16 17:17:49 +01:00
DebaucheryLibrarian
57d46f5842 Fixed latest query in Gamma scraper. 2022-01-16 17:17:45 +01:00
DebaucheryLibrarian
7f11d0ca91 1.204.9 2022-01-12 17:50:27 +01:00
DebaucheryLibrarian
bbd788699e Fixed GraphQL query for scenes REST API. Added entry ID to scene search document. 2022-01-12 17:50:23 +01:00
DebaucheryLibrarian
e24add98ea 1.204.8 2022-01-10 02:17:21 +01:00
DebaucheryLibrarian
506971b44b Chunked duplicate check to prevent postgres stack depth errors. 2022-01-10 02:17:17 +01:00
DebaucheryLibrarian
43a0bc8a2c 1.204.7 2022-01-07 01:07:43 +01:00
DebaucheryLibrarian
baebdbc0bb Adding comment listing sister sites for non-exclusive Gamma API scenes. 2022-01-07 01:07:41 +01:00
DebaucheryLibrarian
1c24cae3b1 1.204.6 2022-01-07 01:02:36 +01:00
DebaucheryLibrarian
b9146aee5f Removed exclusive filter from Evil Angel parameters. 2022-01-07 01:02:34 +01:00
DebaucheryLibrarian
09bfcb01f5 1.204.5 2022-01-06 01:41:29 +01:00
DebaucheryLibrarian
5d80b6dc21 Added and improved Reality Kings logos. Added tag photos. 2022-01-06 01:41:26 +01:00
DebaucheryLibrarian
140a38c349 1.204.4 2021-12-31 01:45:06 +01:00
DebaucheryLibrarian
6760c11881 Disabled why-is-node-running to assess OOM. 2021-12-31 01:45:03 +01:00
DebaucheryLibrarian
2dcdbf9c37 1.204.3 2021-12-27 22:36:03 +01:00
DebaucheryLibrarian
3b958a02ef Added memory profiler configuration options. 2021-12-27 22:36:01 +01:00
DebaucheryLibrarian
605242b399 1.204.2 2021-12-27 17:29:33 +01:00
DebaucheryLibrarian
299f257a01 Logging memory usage on media fetch. 2021-12-27 17:29:23 +01:00
DebaucheryLibrarian
0ad64ef5f4 1.204.1 2021-12-20 03:29:16 +01:00
DebaucheryLibrarian
3bfe1e8fdb Storing scenes before fetching movies. 2021-12-20 03:29:14 +01:00
DebaucheryLibrarian
7d992f4129 Added triggers to memory dump. 2021-12-20 02:28:59 +01:00
DebaucheryLibrarian
280032937f Added centralized movie page fetcher. Added memory heap dump. 2021-12-20 02:22:10 +01:00
DebaucheryLibrarian
d93670842b 1.204.0 2021-12-19 23:03:48 +01:00
DebaucheryLibrarian
dc187a9a3a Added execute method to qu, removed runScripts from Gamma's fetchMovie to observe effect on memory usage. 2021-12-19 23:03:44 +01:00
DebaucheryLibrarian
5f89c6e14c 1.203.13 2021-12-19 00:08:22 +01:00
DebaucheryLibrarian
9f10420eb9 Fixed tag search GraphQL query. 2021-12-19 00:08:21 +01:00
DebaucheryLibrarian
9568370132 1.203.12 2021-12-18 23:44:54 +01:00
DebaucheryLibrarian
1c0c30be11 Improved and re-enabled why-is-node-running logger. 2021-12-18 23:44:51 +01:00
DebaucheryLibrarian
cdb7dcd6e9 1.203.11 2021-12-18 23:22:38 +01:00
DebaucheryLibrarian
8e4be06675 Closing JSDOM window after deep scrapes in an attempt to save memory. 2021-12-18 23:22:35 +01:00
DebaucheryLibrarian
de7a8c756a 1.203.10 2021-12-13 01:20:04 +01:00
DebaucheryLibrarian
65e2b72c6a Fixed Gamma movie scraper failing when data object is not available. 2021-12-13 01:20:02 +01:00
DebaucheryLibrarian
523c36ecd4 1.203.9 2021-12-11 22:46:58 +01:00
DebaucheryLibrarian
5b5688438c Fixed Gamma movie scene query. Added tag photos. 2021-12-11 22:46:55 +01:00
DebaucheryLibrarian
4ca07631c7 1.203.8 2021-12-05 23:59:20 +01:00
DebaucheryLibrarian
ec4e7bc12a Updated Jules Jordan scraper for Sperm Swallowers and The Ass Factory. 2021-12-05 23:59:17 +01:00
DebaucheryLibrarian
26d15c0a6f Updated Jules Jordan scraper for Manuel Ferrara and Girl Girl. 2021-12-05 23:43:23 +01:00
DebaucheryLibrarian
a484396db4 Hiding scenes with missing dates from network overview. Using computed effective date column. 2021-12-05 23:29:27 +01:00
DebaucheryLibrarian
681db02784 1.203.7 2021-12-05 02:54:57 +01:00
DebaucheryLibrarian
e88554666c Reduced media concurrency to assess effect on memory. Moved qu context removal to deep scrape runner. Updated movie graphql queries. 2021-12-05 02:54:55 +01:00
DebaucheryLibrarian
9f37ec4cff 1.203.6 2021-12-04 00:32:31 +01:00
DebaucheryLibrarian
abd063a578 Removed window map from http module to prevent memory pile-up with update scraping. 2021-12-04 00:32:28 +01:00
DebaucheryLibrarian
883e57ce1f 1.203.5 2021-12-02 15:51:33 +01:00
DebaucheryLibrarian
ad04502a8c Added Radical favicons. 2021-12-02 15:51:30 +01:00
DebaucheryLibrarian
f65ed16842 1.203.4 2021-12-02 00:45:24 +01:00
DebaucheryLibrarian
1918dd4ad5 Removing query methods from XEmpire release. 2021-12-02 00:45:22 +01:00
DebaucheryLibrarian
3983d3464f 1.203.3 2021-12-01 23:44:27 +01:00
DebaucheryLibrarian
61daf5f037 Added debug log for jsdom window closing. 2021-12-01 23:44:25 +01:00
DebaucheryLibrarian
eafa144104 1.203.2 2021-12-01 23:39:11 +01:00
DebaucheryLibrarian
60c151ff6a Reduced deep scrape concurrency to 1. 2021-12-01 23:39:09 +01:00
DebaucheryLibrarian
c97d18fcf9 1.203.1 2021-12-01 23:30:12 +01:00
DebaucheryLibrarian
e41f9fa937 Added waitImmediate to deep scrape, reduced concurrency. 2021-12-01 23:30:10 +01:00
DebaucheryLibrarian
56a7fb0ad9 1.203.0 2021-12-01 17:26:34 +01:00
DebaucheryLibrarian
e29cbc9fea Closing JSDOM window after deep scrape in an attempt to save memory. Reduced deep scrape concurrency to 5. 2021-12-01 17:26:13 +01:00
DebaucheryLibrarian
08f725a0b6 1.202.3 2021-12-01 00:00:26 +01:00
DebaucheryLibrarian
b93a5715cb Updated Jules Jordan scraper for new update page layout. 2021-12-01 00:00:24 +01:00
DebaucheryLibrarian
2018d5c298 1.202.2 2021-11-29 02:49:12 +01:00
DebaucheryLibrarian
87094a9498 Replaced cheerio with qu in Gamma scraper. 2021-11-29 02:49:07 +01:00
DebaucheryLibrarian
53a1c697d0 1.202.1 2021-11-28 01:20:41 +01:00
DebaucheryLibrarian
f04eb72891 Added upcoming scraper to PurgatoryX. 2021-11-28 01:20:39 +01:00
DebaucheryLibrarian
dfeb0c08cf 1.202.0 2021-11-27 23:55:22 +01:00
DebaucheryLibrarian
9d7183ac69 Added PurgatoryX scraper. 2021-11-27 23:55:16 +01:00
DebaucheryLibrarian
2539e88f47 1.201.6 2021-11-22 02:51:55 +01:00
DebaucheryLibrarian
20d0d860d3 Fixed MindGeek scraper trying to acquire session from mindgeek.com 2021-11-22 02:51:52 +01:00
DebaucheryLibrarian
6b4aa64d74 Improved MindGeek scraper session check to prevent crash when network session isn't available yet. 2021-11-22 02:44:03 +01:00
DebaucheryLibrarian
c7b9ec7a4b 1.201.5 2021-11-21 00:47:34 +01:00
DebaucheryLibrarian
e81864ebde Removed verbose http queued output. 2021-11-21 00:47:32 +01:00
DebaucheryLibrarian
0305a22a24 Removed stray console log from media module. 2021-11-21 00:43:35 +01:00
DebaucheryLibrarian
a406eb584d 1.201.4 2021-11-21 00:41:15 +01:00
DebaucheryLibrarian
2be3ea9bbb Explicitly adding headers to http module result. 2021-11-21 00:41:12 +01:00
DebaucheryLibrarian
0a8a7ff9a5 1.201.3 2021-11-21 00:31:12 +01:00
DebaucheryLibrarian
98c103c625 Fixed uuid import. Changed fs rmdir to rm. 2021-11-21 00:31:09 +01:00
DebaucheryLibrarian
e0908a5f5e 1.201.2 2021-11-21 00:19:13 +01:00
DebaucheryLibrarian
f0b7678444 Fixed upsert failing on empty insert array due breaking Knex API change. 2021-11-21 00:19:10 +01:00
DebaucheryLibrarian
64e9efe095 1.201.1 2021-11-21 00:15:21 +01:00
DebaucheryLibrarian
ab29ab053e Increased tile heart button size. Hiding entity tile info under hover effect. 2021-11-21 00:15:19 +01:00
DebaucheryLibrarian
a5081bc7c9 Updated primary color. 2021-11-21 00:04:21 +01:00
DebaucheryLibrarian
da9c628f9b 1.201.0 2021-11-21 00:00:14 +01:00
DebaucheryLibrarian
26539b74a5 Updated dependencies. Added periodic memory logger. 2021-11-20 23:59:48 +01:00
DebaucheryLibrarian
ccb99e278c Added periodic memory logger. 2021-11-20 23:59:15 +01:00
DebaucheryLibrarian
a867817dc1 Improved scene tile scaling. 2021-10-31 01:00:12 +02:00
DebaucheryLibrarian
d1e05915b5 Tweaked scene and actor tile design, tags grid breakpoints. 2021-10-31 00:31:18 +02:00
DebaucheryLibrarian
b764fdec85 Showing scene and channel count on entity page and tile. 2021-10-30 22:41:58 +02:00
DebaucheryLibrarian
8322d43b09 1.200.2 2021-10-28 02:10:37 +02:00
DebaucheryLibrarian
29b8c5e38e Including unextracted scenes in date determination. 2021-10-28 02:10:30 +02:00
DebaucheryLibrarian
83dd233991 1.200.1 2021-10-28 02:00:04 +02:00
DebaucheryLibrarian
0864154a0e Added unextracted property to keep paginating when extracting scenes. 2021-10-28 01:59:53 +02:00
DebaucheryLibrarian
53357d4bd2 1.200.0 2021-10-27 17:19:33 +02:00
DebaucheryLibrarian
69bf98edf1 1.199.17 2021-10-27 17:19:30 +02:00
DebaucheryLibrarian
a22c4d5679 Added beforeNetwork hook, used by MindGeek. Added Filthy Kings to Gamma. 2021-10-27 17:19:23 +02:00
DebaucheryLibrarian
e5ad1648eb 1.199.16 2021-10-26 23:42:42 +02:00
DebaucheryLibrarian
100a35b4e8 Added before scene fetch method to prevent e.g. unnecessary session requests, moved scraper assignment to entity lookup. Removed channel URL hostname matching.. 2021-10-26 23:42:32 +02:00
DebaucheryLibrarian
6c5d4389fe Not parsing HTML with jsdom when using http module directly to save memory. Added loading ellipsis to release grid pages. 2021-10-25 02:06:24 +02:00
DebaucheryLibrarian
92f9ff4104 1.199.15 2021-10-20 01:47:04 +02:00
DebaucheryLibrarian
0e4fd12d70 Added more loggers to http module. 2021-10-20 01:46:56 +02:00
DebaucheryLibrarian
9040285ce5 1.199.14 2021-10-17 23:57:10 +02:00
DebaucheryLibrarian
972b15e72d Added memory profiling. 2021-10-17 23:56:49 +02:00
DebaucheryLibrarian
c1aea78496 1.199.13 2021-10-17 19:59:11 +02:00
DebaucheryLibrarian
49f891ba44 Ignoring 1-second scene duration from MindGeek API. 2021-10-17 19:59:05 +02:00
DebaucheryLibrarian
239057f1b6 1.199.12 2021-10-17 00:28:18 +02:00
DebaucheryLibrarian
167df35d37 Fixed tags module not dealing with empty releases or tags, added origin tag column to chapter tag table. 2021-10-17 00:28:13 +02:00
DebaucheryLibrarian
b1b2ad2111 1.199.11 2021-10-11 04:31:46 +02:00
DebaucheryLibrarian
f1ff662be2 Fixed tags seed file. 2021-10-11 04:31:40 +02:00
DebaucheryLibrarian
2418fec9c9 1.199.10 2021-10-11 02:16:58 +02:00
DebaucheryLibrarian
aaaa0a6afb Updated proxy list. 2021-10-11 02:16:51 +02:00
DebaucheryLibrarian
afe22003e1 1.199.9 2021-10-10 00:04:28 +02:00
DebaucheryLibrarian
b6ad2903f3 Storing original tags. 2021-10-10 00:04:21 +02:00
DebaucheryLibrarian
6c298cd639 1.199.8 2021-09-28 20:45:30 +02:00
DebaucheryLibrarian
dbff3e9539 Added Brad Montana. Added teaser link to trailer video. 2021-09-28 20:45:22 +02:00
DebaucheryLibrarian
fcc6b33d07 1.199.7 2021-09-17 04:28:54 +02:00
DebaucheryLibrarian
0224c26ca0 Fixed alert searches. Added Teen Mega World campaigns. 2021-09-17 04:28:48 +02:00
DebaucheryLibrarian
294f15e694 1.199.6 2021-09-17 03:30:58 +02:00
DebaucheryLibrarian
522584711b Added Teen Mega World scraper. 2021-09-17 03:30:49 +02:00
DebaucheryLibrarian
ae40f33283 Added Transfixed to Adult Time. 2021-09-16 16:57:14 +02:00
DebaucheryLibrarian
172e01c80e Added Score to default ignored networks. 2021-09-15 02:01:13 +02:00
DebaucheryLibrarian
901acf1390 1.199.5 2021-09-14 00:42:59 +02:00
DebaucheryLibrarian
a316da3083 Retrieving Spizoo trailers. 2021-09-14 00:42:51 +02:00
DebaucheryLibrarian
0b46e27497 1.199.4 2021-09-13 01:29:47 +02:00
DebaucheryLibrarian
b164c5dad7 Added Spizoo. 2021-09-13 01:29:39 +02:00
DebaucheryLibrarian
2d6b285817 1.199.3 2021-09-12 23:21:45 +02:00
DebaucheryLibrarian
65c79567d2 Added unstash buttons to non-favorites stash items 2021-09-12 23:21:39 +02:00
DebaucheryLibrarian
d3633f31ac 1.199.2 2021-09-12 00:33:30 +02:00
DebaucheryLibrarian
73a9a11aa6 Added favorites link to user menu. 2021-09-12 00:33:24 +02:00
DebaucheryLibrarian
c21bd8b01e 1.199.1 2021-09-12 00:24:42 +02:00
DebaucheryLibrarian
14f4d24872 Fixed movie stash hover color. Added secondary release date sorting on stashed releases. 2021-09-12 00:24:36 +02:00
DebaucheryLibrarian
731abc79ee 1.199.0 2021-09-12 00:05:47 +02:00
DebaucheryLibrarian
d542889827 Added sections and pagination to stash page. 2021-09-12 00:05:45 +02:00
DebaucheryLibrarian
8c5ef21459 1.198.11 2021-08-30 01:37:22 +02:00
DebaucheryLibrarian
7bfca9596d Appending movie title if scene title only contains 'scene x' 2021-08-30 01:37:19 +02:00
DebaucheryLibrarian
7bd858f96b Generalized Adult Empire subsite scraper, added West Coast Productions. 2021-08-30 01:13:32 +02:00
DebaucheryLibrarian
b8657cb6e6 1.198.10 2021-08-26 01:14:58 +02:00
DebaucheryLibrarian
69a7e8f13d Fixed PornDoe scraper. 2021-08-26 01:14:54 +02:00
DebaucheryLibrarian
23537e0243 Removed Gamma console logs. 2021-08-24 02:56:59 +02:00
DebaucheryLibrarian
546f778b1b 1.198.9 2021-08-24 02:56:03 +02:00
DebaucheryLibrarian
245f69a122 Fixed movie bindings. 2021-08-24 02:54:40 +02:00
DebaucheryLibrarian
c6f3f7a239 1.198.8 2021-08-23 02:38:18 +02:00
DebaucheryLibrarian
f0a6e80e5a Reset pagination on search, scroll into view when paginating search. 2021-08-23 02:38:16 +02:00
DebaucheryLibrarian
0cbb985945 1.198.7 2021-08-23 01:54:24 +02:00
DebaucheryLibrarian
bb384029ac Disabled eager searching except for entities. Updating movie search document on store. 2021-08-23 01:54:22 +02:00
DebaucheryLibrarian
85c6b581cb Fixed movie limit. 2021-08-23 01:45:18 +02:00
DebaucheryLibrarian
dd18644796 1.198.6 2021-08-23 01:44:32 +02:00
DebaucheryLibrarian
1628e41d09 Fixed actors and movies pagination scroll. 2021-08-23 01:44:30 +02:00
DebaucheryLibrarian
a77d8f4cea 1.198.5 2021-08-23 01:35:48 +02:00
DebaucheryLibrarian
0a4152b4b7 Fixed actor search query. 2021-08-23 01:35:46 +02:00
DebaucheryLibrarian
3fc4dc15df 1.198.4 2021-08-23 01:29:49 +02:00
DebaucheryLibrarian
4ee0dcef9b Added country filter for actors. 2021-08-23 01:29:46 +02:00
DebaucheryLibrarian
011482ac9d 1.198.3 2021-08-22 22:25:23 +02:00
DebaucheryLibrarian
6a8c9d89cb Using paginated full text search for movies, combined actor search and fetch to allow combining search with filters. 2021-08-22 22:25:20 +02:00
DebaucheryLibrarian
e0905ab8fc 1.198.2 2021-08-22 03:14:08 +02:00
DebaucheryLibrarian
eb1f8f86fd Added search to tags. 2021-08-22 03:14:02 +02:00
DebaucheryLibrarian
959b5d9d0e 1.198.1 2021-08-22 01:26:12 +02:00
DebaucheryLibrarian
0c19a026ef Replaced alphabet index with search bar on actors page. 2021-08-22 01:26:09 +02:00
DebaucheryLibrarian
b24973eb19 1.198.0 2021-08-22 00:40:27 +02:00
DebaucheryLibrarian
4b18867883 Added pagination and search to movies page. 2021-08-22 00:40:22 +02:00
DebaucheryLibrarian
5e292a0880 1.197.4 2021-08-17 19:25:14 +02:00
DebaucheryLibrarian
f00e37490c Finished Cum Louder scraper, updated Vixen scraper. Added tag posters. 2021-08-17 19:25:10 +02:00
DebaucheryLibrarian
715e44cf21 1.197.3 2021-08-15 16:56:00 +02:00
DebaucheryLibrarian
25d1c1b229 Fixed incomplete scene tile studio link breaking search. 2021-08-15 16:55:54 +02:00
DebaucheryLibrarian
094226eeb5 Added new LegalPorno/AnalVids studios. 2021-08-15 15:28:13 +02:00
DebaucheryLibrarian
b08cb46ae5 Removed stray console log. 2021-08-15 13:21:02 +02:00
DebaucheryLibrarian
6b88cf1040 1.197.2 2021-08-15 13:16:50 +02:00
DebaucheryLibrarian
aacfd1b29d Updated object-merge-advanced API use. 2021-08-15 13:16:48 +02:00
DebaucheryLibrarian
0427e1e276 1.197.1 2021-08-15 04:28:59 +02:00
DebaucheryLibrarian
ee33bd8e63 Upgraded object-merge-advanced. 2021-08-15 04:28:56 +02:00
DebaucheryLibrarian
25b8bd689e Rebuild. 2021-08-14 22:42:50 +02:00
DebaucheryLibrarian
b6468b03a7 1.197.0 2021-08-09 10:31:25 +02:00
DebaucheryLibrarian
a848d6991b Added Diabolic and Cum Louder, added content type expect option to media sources to fix Vixen thumbnails. 2021-08-09 10:31:12 +02:00
DebaucheryLibrarian
65c3053b49 Fixed affiliate seed flush order. 2021-07-12 01:48:01 +02:00
DebaucheryLibrarian
1d686d7e40 1.196.6 2021-07-12 01:41:22 +02:00
DebaucheryLibrarian
488d1082e4 Added parameter affiliates. 2021-07-12 01:41:18 +02:00
DebaucheryLibrarian
8967907893 1.196.5 2021-07-06 00:01:51 +02:00
DebaucheryLibrarian
e527a67dc1 Merge branch 'experimental' 2021-07-06 00:01:47 +02:00
DebaucheryLibrarian
6847ef690c Added Arch Angel, updated BAM Visions scraper to accomodate Arch Angel (different network, same unidentified CMS). 2021-07-06 00:01:44 +02:00
DebaucheryLibrarian
96a2125248 Added tag photos. 2021-07-05 15:54:37 +02:00
DebaucheryLibrarian
c5e4310a6b 1.196.4 2021-07-05 00:06:26 +02:00
DebaucheryLibrarian
23b41fc4f3 Fixed Bang scraper. Added Kink affiliate, tag photos. 2021-07-05 00:06:18 +02:00
DebaucheryLibrarian
4fb41a4c35 1.196.3 2021-06-28 18:45:46 +02:00
DebaucheryLibrarian
12c0e8e828 Fixed login link in sidebar. 2021-06-28 18:45:38 +02:00
DebaucheryLibrarian
dce27e985e 1.196.2 2021-06-28 05:13:48 +02:00
DebaucheryLibrarian
0a343dfa98 Improved campaign component, added various banners. 2021-06-28 05:13:41 +02:00
DebaucheryLibrarian
729ca0f968 1.196.1 2021-06-28 02:50:10 +02:00
DebaucheryLibrarian
385dfb9f75 Removed affiliate table in favor of direct campaign URLs. 2021-06-28 02:50:06 +02:00
DebaucheryLibrarian
afbae24f43 1.196.0 2021-06-28 00:05:32 +02:00
DebaucheryLibrarian
eb7009832a Added rudimentary affiliate banner setup. Separated login and signup disable. Added various tag photos. 2021-06-28 00:05:24 +02:00
DebaucheryLibrarian
d1480da076 Added effective date column. Changed warning page theme. 2021-06-19 18:09:58 +02:00
DebaucheryLibrarian
10a2731caf 1.195.0 2021-06-13 16:49:42 +02:00
DebaucheryLibrarian
ab1329dd67 Updating entity ID for rescraped scenes with network entry IDs enabled. 2021-06-13 16:49:27 +02:00
DebaucheryLibrarian
e9a0700742 Improved upcoming update query. 2021-06-06 01:09:32 +02:00
DebaucheryLibrarian
3f473589ad Using bulk insert utility for alert notifications to prevent duplicate errors (fixed). 2021-06-04 03:22:40 +02:00
DebaucheryLibrarian
7a44c7aaaa 1.194.1 2021-06-04 03:10:47 +02:00
DebaucheryLibrarian
bed329cd8c Using bulk insert utility for alert notifications to prevent duplicate errors. 2021-06-04 03:10:41 +02:00
DebaucheryLibrarian
011bb4efa3 1.194.0 2021-06-02 03:27:37 +02:00
DebaucheryLibrarian
c979173422 Rescraping upcoming scenes. Fixed language and scene deep scraping for Dorcel scraper. 2021-06-02 03:27:32 +02:00
DebaucheryLibrarian
42791c528e 1.193.3 2021-05-20 00:06:31 +02:00
DebaucheryLibrarian
c76c8054b9 Updated repository owner and address. 2021-05-20 00:06:28 +02:00
DebaucheryLibrarian
6107c7d0ef Added tag photos. 2021-05-19 23:27:36 +02:00
DebaucheryLibrarian
d0d045a2ab Fixed dark theme text color in alert search. 2021-05-15 22:55:50 +02:00
DebaucheryLibrarian
102e053021 Fixed missing lazy avatar from stash actor preview query. 2021-05-15 22:16:42 +02:00
DebaucheryLibrarian
ca0660c1cc Fixed see more notifications link not closing tooltip. 2021-05-15 22:04:32 +02:00
DebaucheryLibrarian
a4a05232db 1.193.2 2021-05-15 22:02:03 +02:00
DebaucheryLibrarian
0f8d5d4456 Fixed alert dialog button in notifications, fixed add tile padding on profile page. 2021-05-15 22:01:57 +02:00
DebaucheryLibrarian
478a2c4b48 Always showing alerts section on profile. 2021-05-15 20:38:16 +02:00
DebaucheryLibrarian
55e240e68d Re-added alerts to profile. 2021-05-15 03:49:27 +02:00
DebaucheryLibrarian
71b25774d0 Removed unused variable causing failed build. 2021-05-15 03:35:10 +02:00
DebaucheryLibrarian
1697728b2a Temporarily disabled alerts to address database issues. 2021-05-15 03:32:55 +02:00
DebaucheryLibrarian
4242efbd4c Patched user profile breaking when alerts can't be loaded. 2021-05-15 03:24:21 +02:00
DebaucheryLibrarian
0b825a61bb 1.193.1 2021-05-15 03:07:04 +02:00
DebaucheryLibrarian
7d974e6b89 Patched notifications to handle empty result. 2021-05-15 03:06:57 +02:00
DebaucheryLibrarian
ae1b9c0d73 Fixed v-deep selector in banner. 2021-05-15 02:55:24 +02:00
DebaucheryLibrarian
91ebcace0a 1.193.0 2021-05-15 02:51:59 +02:00
DebaucheryLibrarian
846b860c06 Hiding scene photos and trailers from guests. 2021-05-15 02:51:52 +02:00
DebaucheryLibrarian
83ed793e39 Added dedicated notifications page. 2021-05-09 00:23:10 +02:00
DebaucheryLibrarian
3f55b90ab8 Adding alerted scene to stashes. 2021-04-29 01:45:01 +02:00
DebaucheryLibrarian
4806b0aa41 Improved notifications design. 2021-04-27 04:41:22 +02:00
DebaucheryLibrarian
3b91493995 Using Tippy.js for directive tooltips. 2021-04-27 03:56:38 +02:00
DebaucheryLibrarian
8bf9fff7dc Triggering notifications for children of alert entities. Showing icons in alert entity search to distinguish networks and channels. 2021-04-26 00:48:31 +02:00
DebaucheryLibrarian
eed563e06f Updating video player when switching scene page. 2021-04-25 04:20:38 +02:00
DebaucheryLibrarian
fc1c2fc2f3 Added notification clear, improved notification styling. 2021-04-25 03:08:50 +02:00
DebaucheryLibrarian
f8a3bf6a64 Updated scene URLs in Vixen scraper. 2021-04-22 19:49:11 +02:00
DebaucheryLibrarian
c5e74c33b7 Improved alert notifications. 2021-04-22 19:44:23 +02:00
DebaucheryLibrarian
95f3b1c03a Added rudimentary notifications for set alerts. 2021-04-17 01:10:45 +02:00
DebaucheryLibrarian
0773a8019c Updated release search regex to exclude underscores. 2021-04-15 16:56:44 +02:00
DebaucheryLibrarian
1116e09af5 Added Discord link to footer. 2021-04-15 16:11:16 +02:00
DebaucheryLibrarian
52e215d3bc Added tag poster 2021-04-11 15:50:02 +02:00
DebaucheryLibrarian
7f25846d55 List alerts in profile 2021-04-05 00:48:03 +02:00
DebaucheryLibrarian
d36e52d5d1 Added row level security to alert tables. Added alerts to user query. 2021-04-04 22:52:54 +02:00
DebaucheryLibrarian
da0cbced15 Added alert dialog. Fixed image rotation EXIT data being discarded. 2021-04-04 21:52:19 +02:00
DebaucheryLibrarian
837fc98ad2 1.192.2 2021-03-29 23:58:49 +02:00
DebaucheryLibrarian
a0f41da80a Addressing CORS issues with video VR. 2021-03-29 23:58:41 +02:00
DebaucheryLibrarian
4a2d2ad996 1.192.1 2021-03-29 22:47:48 +02:00
DebaucheryLibrarian
010da8954b Fixed MYLF scraper failing when channel is missing, fixed profile measurement matching. Added MYLF Selects channel. 2021-03-29 22:47:43 +02:00
DebaucheryLibrarian
e643e0a924 1.192.0 2021-03-29 22:23:21 +02:00
DebaucheryLibrarian
c386a9098f Generalized Team Skeet scraper, added MYLF network and various Team Skeet partner channels. 2021-03-29 22:22:56 +02:00
DebaucheryLibrarian
d17dbf1b36 1.191.3 2021-03-26 01:47:58 +01:00
DebaucheryLibrarian
3961f83ef6 Fixed animated poster being overwritten as jpeg. 2021-03-26 01:47:40 +01:00
DebaucheryLibrarian
d0648b5006 Updated favicon manifest. 2021-03-24 21:49:24 +01:00
DebaucheryLibrarian
337b6c70f4 1.191.2 2021-03-24 19:47:33 +01:00
DebaucheryLibrarian
2cd007dae3 Updated favicon. 2021-03-24 19:47:25 +01:00
DebaucheryLibrarian
43e55446f7 1.191.1 2021-03-24 17:17:57 +01:00
DebaucheryLibrarian
f47be86df3 Added scroll events to inner content divs to sync tooltips with page. Including actor heart button on stash page. Fixed stash scene preview title overflow. 2021-03-24 17:17:51 +01:00
DebaucheryLibrarian
d5bf253011 1.191.0 2021-03-24 01:52:34 +01:00
DebaucheryLibrarian
093d447328 Added heart button to actor tiles. 2021-03-24 01:52:27 +01:00
DebaucheryLibrarian
e12de5ec00 1.190.6 2021-03-24 01:26:40 +01:00
DebaucheryLibrarian
a64b25eb51 Added heart button to search results. Changed warning page button design. 2021-03-24 01:26:33 +01:00
DebaucheryLibrarian
bbf058480f Added unused Wifey's World logos. 2021-03-23 21:05:03 +01:00
DebaucheryLibrarian
b2105c8fb0 Refined dark theme. 2021-03-23 20:37:20 +01:00
DebaucheryLibrarian
8ff5a8c5e1 1.190.5 2021-03-23 17:32:56 +01:00
DebaucheryLibrarian
98624c9954 Fixed text shadow in stash scene previews. 2021-03-23 17:32:50 +01:00
DebaucheryLibrarian
3b9e8e3cc3 1.190.4 2021-03-23 15:25:24 +01:00
DebaucheryLibrarian
193af9bab5 Fixed session options in http module. 2021-03-23 15:25:21 +01:00
DebaucheryLibrarian
b2ad031c54 Added tag photos. Removed brackets from actor filter range digits. 2021-03-22 00:35:55 +01:00
DebaucheryLibrarian
0419cc633b Using lazy image instead of full avatar for stash preview actors. 2021-03-21 17:28:46 +01:00
DebaucheryLibrarian
63143fb185 Fixed yet another missing S3 field in avatar query. 2021-03-21 17:23:15 +01:00
DebaucheryLibrarian
3f6ecfd92c Fixed another missing S3 field in avatar query. 2021-03-21 15:14:28 +01:00
DebaucheryLibrarian
a208c922f7 1.190.3 2021-03-21 14:22:37 +01:00
DebaucheryLibrarian
757d554e7b Fixed S3 missing from actor avatar queries. Improved stash button alignment. 2021-03-21 14:22:31 +01:00
DebaucheryLibrarian
74afc55dc6 Improved user page stash paddings. 2021-03-21 13:51:33 +01:00
DebaucheryLibrarian
29b0451608 Fixed actor tile lazy photo misalignment. 2021-03-21 13:45:49 +01:00
DebaucheryLibrarian
cfa1ed7a61 1.190.2 2021-03-21 04:34:09 +01:00
DebaucheryLibrarian
c70f500acc Fixed stashed check breaking on empty stashes in PostGraphile plugins. 2021-03-21 04:34:04 +01:00
DebaucheryLibrarian
76a5ccf3f6 1.190.1 2021-03-21 04:08:49 +01:00
DebaucheryLibrarian
35c28dede2 Fixed favorite status on scene tile. 2021-03-21 04:08:46 +01:00
DebaucheryLibrarian
11e043ca2e 1.190.0 2021-03-21 03:58:48 +01:00
DebaucheryLibrarian
7ac64c57ae Added extended heart button to actor component, fixed movie stash query. 2021-03-21 03:58:13 +01:00
DebaucheryLibrarian
9ff70e5578 Separated full heart button into component. 2021-03-21 03:46:59 +01:00
DebaucheryLibrarian
348aa91832 Added stash menu to release page, returning stashes from stash API to avoid reloading or local interpolation. 2021-03-21 03:23:58 +01:00
DebaucheryLibrarian
de5d104e1e Improved responsiveness of stash header. 2021-03-20 23:40:05 +01:00
DebaucheryLibrarian
565cf551f0 Added browse text to stash link to clarify link. 2021-03-20 23:27:53 +01:00
DebaucheryLibrarian
819d53fc2b Fixed dark theme for heart icons and stash scene previews. 2021-03-20 23:20:07 +01:00
DebaucheryLibrarian
67f22a6e08 Hiding remove stash icons from other users. 2021-03-20 23:07:47 +01:00
DebaucheryLibrarian
eee47111a6 Added delete stash icons and dialog. 2021-03-20 23:03:13 +01:00
DebaucheryLibrarian
07643870cd Updating stash button locally on actor and scene page. 2021-03-20 18:12:06 +01:00
DebaucheryLibrarian
bb949e0a3b Improved user page stash display on narrow pages. 2021-03-20 16:47:01 +01:00
DebaucheryLibrarian
a7cf3f689e Fixed actor photo width on profile page. 2021-03-20 03:36:16 +01:00
DebaucheryLibrarian
d4919016b6 1.189.1 2021-03-20 03:33:34 +01:00
DebaucheryLibrarian
67af9f2ea2 Using thumbnail width and height for release banner photos. Preventing user page from reloading when closing the add stash dialog without adding stash. 2021-03-20 03:33:29 +01:00
DebaucheryLibrarian
bb9d6ee8fc Added dialog to add stashes. 2021-03-20 03:22:08 +01:00
DebaucheryLibrarian
e88cf4e3f4 Separated user page stash component. 2021-03-20 02:49:17 +01:00
DebaucheryLibrarian
5577e4fee5 Improved user stash actor previews. 2021-03-20 02:34:49 +01:00
DebaucheryLibrarian
489d253a48 Using full header height for stash header items. 2021-03-20 02:29:52 +01:00
DebaucheryLibrarian
06e6d3940b Refreshing stash page when unstashing scene. Addressed stash preview overflowing on user page. 2021-03-20 02:23:24 +01:00
DebaucheryLibrarian
42a4fe581f 1.189.0 2021-03-20 02:15:37 +01:00
DebaucheryLibrarian
292faa1e48 Added public visibility toggle to stash page. 2021-03-20 02:15:31 +01:00
DebaucheryLibrarian
4bc6ff846d Added public visibility toggle to user page stashes. 2021-03-20 02:03:30 +01:00
DebaucheryLibrarian
011f10fba8 No longer reloading when stashing scene, immediately toggling heart locally and resetting on dispatch error. 2021-03-20 00:41:21 +01:00
DebaucheryLibrarian
d0e987a2aa 1.188.2 2021-03-20 00:16:26 +01:00
DebaucheryLibrarian
6e8af52237 Decreased subheader disclaimer padding. 2021-03-20 00:15:33 +01:00
DebaucheryLibrarian
e301e2184c Fixed undefined user ID in postgres function. Fixed and improved mobile alignment for new and stash icons on scene tile. 2021-03-20 00:12:12 +01:00
DebaucheryLibrarian
3b3f4a1f2d Added user links to sidebar. 2021-03-19 21:57:04 +01:00
DebaucheryLibrarian
c8ac8d6564 1.188.1 2021-03-19 04:19:02 +01:00
DebaucheryLibrarian
626cbc4fc5 Updating stash page when stashing scene. Improved layout. 2021-03-19 04:18:56 +01:00
DebaucheryLibrarian
fe2004b3da 1.188.0 2021-03-19 03:28:50 +01:00
DebaucheryLibrarian
fba4cbfb7b Improved scene tile stash icon shadow. 2021-03-19 03:28:44 +01:00
DebaucheryLibrarian
731a2792c5 Added favorite stash heart to scene tiles. 2021-03-19 03:27:48 +01:00
DebaucheryLibrarian
f3d55806d1 1.187.0 2021-03-19 02:36:40 +01:00
DebaucheryLibrarian
f0265c2f5d Added dedicated stash page. Using preview tiles for stashes on user page. 2021-03-19 02:36:31 +01:00
DebaucheryLibrarian
cc27f202af 1.186.3 2021-03-18 19:44:31 +01:00
DebaucheryLibrarian
41261adc76 Fixed filter dialog menu link. Improved disclaimer title wrapping. 2021-03-18 19:44:23 +01:00
DebaucheryLibrarian
1a9ded19c5 1.186.2 2021-03-18 04:36:09 +01:00
DebaucheryLibrarian
862a29bb6e Using thumbnail size instead of original photo size in image tags. 2021-03-18 04:36:04 +01:00
DebaucheryLibrarian
0a92586c53 1.186.1 2021-03-18 04:01:42 +01:00
DebaucheryLibrarian
90b3d8a4d6 Compacted warning page. 2021-03-18 04:01:35 +01:00
DebaucheryLibrarian
5a2e93e900 Added various tag photos and descriptions. 2021-03-17 05:11:17 +01:00
DebaucheryLibrarian
4e81a8a1d6 Fixed movie banner using wrong photo variable. 2021-03-17 02:12:56 +01:00
DebaucheryLibrarian
83d3621441 1.186.0 2021-03-17 02:09:43 +01:00
DebaucheryLibrarian
336b91c872 Refactored http timeout handling. 2021-03-17 02:09:34 +01:00
DebaucheryLibrarian
36a8adbd8c 1.185.1 2021-03-16 04:35:31 +01:00
DebaucheryLibrarian
586ff6d4bd Calculating tag photo dimensions in seed file, improved tag photo lazy loading. 2021-03-16 04:35:26 +01:00
DebaucheryLibrarian
6fef87b0f1 1.185.0 2021-03-16 04:12:29 +01:00
DebaucheryLibrarian
0d7a03f3e5 Allowing auth to be disabled in config. 2021-03-16 04:12:05 +01:00
DebaucheryLibrarian
1703e9a541 Moved http timeout cancelation before pipeline to prevent large files from getting canceled. 2021-03-16 03:59:36 +01:00
DebaucheryLibrarian
ece9569d66 Improved content reflow for lazy loading scene banner. 2021-03-16 03:55:20 +01:00
DebaucheryLibrarian
3bebf5bf51 Added tag photos. 2021-03-16 02:31:23 +01:00
DebaucheryLibrarian
398161b03b Added rudimentary timeline to display tag chapters. 2021-03-15 04:59:08 +01:00
DebaucheryLibrarian
1fb7d384fb Merge branch 'master' into experimental 2021-03-15 04:16:36 +01:00
DebaucheryLibrarian
7c7b38e869 Inserting user ID to PostGraphile if available. 2021-03-15 04:16:32 +01:00
DebaucheryLibrarian
8e06d465cb Changed example database users. 2021-03-15 04:15:00 +01:00
DebaucheryLibrarian
a0be8f0aa3 Accounting for missing options in http utility timeout function. 2021-03-15 04:13:09 +01:00
DebaucheryLibrarian
41d06f7e9d 1.184.2 2021-03-15 04:11:17 +01:00
DebaucheryLibrarian
cb447da7d0 Added harder timeouts to http utility. Split owner and query database users. 2021-03-15 04:11:14 +01:00
DebaucheryLibrarian
77b40817f2 Added favorites button to actor page. 2021-03-15 03:30:47 +01:00
DebaucheryLibrarian
e371e9725a Added stashes with experimental row security policies. Added tag photos. 2021-03-14 04:54:53 +01:00
DebaucheryLibrarian
816529b0ca Added user sign up and login. 2021-03-13 04:26:24 +01:00
DebaucheryLibrarian
99cfd3dc3f 1.184.1 2021-03-11 16:45:02 +01:00
DebaucheryLibrarian
7c4b9063a7 Centered actor lazy avatar, fixed tag favicon size on mobile. 2021-03-11 16:44:59 +01:00
DebaucheryLibrarian
e2e29a8dbb 1.184.0 2021-03-11 15:53:58 +01:00
DebaucheryLibrarian
66ffa420f8 Merge branch 'experimental' 2021-03-11 15:53:45 +01:00
DebaucheryLibrarian
c33f193a0c Added tag photos. 2021-03-11 15:53:37 +01:00
DebaucheryLibrarian
00c06778ef Only scraping profile source matching actor entity, changed avatar entropy cut-off from 6 to 5.5. 2021-03-11 04:16:59 +01:00
DebaucheryLibrarian
1aab492f38 1.183.0 2021-03-10 04:14:03 +01:00
DebaucheryLibrarian
7f53f585c0 Added tag photos. 2021-03-10 04:13:41 +01:00
DebaucheryLibrarian
5db5d0c7ed Rearranged tag photos in seed file for easier maintenance. 2021-03-10 00:20:50 +01:00
DebaucheryLibrarian
74fcd24a8d Added tag photos. Changed default tag fake to enhanced. 2021-03-10 00:00:50 +01:00
DebaucheryLibrarian
6b8ed89566 1.182.3 2021-03-08 01:26:30 +01:00
DebaucheryLibrarian
0c115e78e2 Updated bhttp. 2021-03-08 01:26:24 +01:00
DebaucheryLibrarian
c7b5611d68 Tag photos. 2021-03-08 01:23:33 +01:00
DebaucheryLibrarian
18684a16eb 1.182.2 2021-03-08 01:07:03 +01:00
DebaucheryLibrarian
a71752b18b Associating directors separately from actors. Added tag photos. 2021-03-08 01:06:57 +01:00
DebaucheryLibrarian
6a9d725633 1.182.1 2021-03-07 20:14:08 +01:00
DebaucheryLibrarian
63ecd3b568 Fixed logo hiding in tag album. Full comment bar links to channel. 2021-03-07 20:14:02 +01:00
DebaucheryLibrarian
1f9963075c Fixed tag descriptions. 2021-03-07 20:05:55 +01:00
DebaucheryLibrarian
d60da3c99e 1.182.0 2021-03-07 20:01:03 +01:00
DebaucheryLibrarian
f91437e03c Fixed SFW determination in image path function. 2021-03-07 20:00:57 +01:00
DebaucheryLibrarian
44523609c1 Removed entity name from tag photo description and appending it dynamically. 2021-03-07 19:47:06 +01:00
DebaucheryLibrarian
de460f53b1 Changed album close behavior so album can be closed when visiting URL directly. 2021-03-07 16:54:20 +01:00
DebaucheryLibrarian
a275d0c855 Smaller tag logo on small displays. Centering tag tile lazy image to prevent load jumps. Including SASS breakpoint variable file automatically. 2021-03-07 16:39:54 +01:00
DebaucheryLibrarian
35cd449e79 1.181.2 2021-03-07 05:11:38 +01:00
DebaucheryLibrarian
e67f029d53 Added various tag photos. 2021-03-07 05:11:27 +01:00
DebaucheryLibrarian
3389dddd08 Added logos to tag photos. 2021-03-07 04:05:25 +01:00
DebaucheryLibrarian
7522404abb 1.181.1 2021-03-07 02:09:45 +01:00
DebaucheryLibrarian
44bb9b33d9 Added intermittent process report to debug freezes. Removed original images. 2021-03-07 02:09:37 +01:00
DebaucheryLibrarian
4a9adbf588 1.181.0 2021-03-07 00:01:09 +01:00
DebaucheryLibrarian
17e6f5a5da Storing directors. 2021-03-07 00:01:02 +01:00
DebaucheryLibrarian
9ae113ab92 Added tag photo. 2021-03-06 04:40:01 +01:00
DebaucheryLibrarian
cd93615b39 1.180.10 2021-03-04 03:24:17 +01:00
DebaucheryLibrarian
fa30fe5169 Added various conditions to prevent errors and warnings. 2021-03-04 02:35:43 +01:00
DebaucheryLibrarian
0c98df232e 1.180.9 2021-03-04 00:31:37 +01:00
DebaucheryLibrarian
254e933740 Added permanent filter for invalid actor associations. 2021-03-04 00:31:31 +01:00
DebaucheryLibrarian
ed7bffd2d6 1.180.8 2021-03-03 23:54:59 +01:00
DebaucheryLibrarian
721e6494cf Added date of birth filter. 2021-03-03 23:54:54 +01:00
DebaucheryLibrarian
96f9c8f01d Only filtering on age when date of birth is not available. 2021-03-03 22:28:52 +01:00
DebaucheryLibrarian
e9ed23abe4 1.180.7 2021-03-03 22:23:53 +01:00
DebaucheryLibrarian
4a963885bc Using range component for all actor range filters. 2021-03-03 22:23:46 +01:00
DebaucheryLibrarian
5c6b5a0668 Added filter range component. Added age filter. 2021-03-03 21:53:10 +01:00
DebaucheryLibrarian
fc6de64311 1.180.6 2021-03-03 20:27:50 +01:00
DebaucheryLibrarian
d94e0ac8fc Highlighting filter trigger when filter is applied. Using binary number as query boolean. 2021-03-03 20:27:45 +01:00
DebaucheryLibrarian
6742bf7d48 Improved range track click position and actor height range. 2021-03-03 19:29:40 +01:00
DebaucheryLibrarian
2cda689b3c 1.180.5 2021-03-03 19:22:03 +01:00
DebaucheryLibrarian
7ae55db7f4 Scoped disclaimer styling. 2021-03-03 19:21:56 +01:00
DebaucheryLibrarian
4d49737536 Added icons. 2021-03-03 19:15:10 +01:00
DebaucheryLibrarian
8ac8e21d78 1.180.4 2021-03-03 19:13:16 +01:00
DebaucheryLibrarian
21ec821b8c Improved actor filter layout and behavior. 2021-03-03 19:13:09 +01:00
DebaucheryLibrarian
74e33303ed Added height and weight filters to actors overview. 2021-03-03 16:47:57 +01:00
DebaucheryLibrarian
cdb47066cc Blocking range track clicks through thumbs. 2021-03-03 14:54:51 +01:00
DebaucheryLibrarian
cf4978b37f Emitting value after clicking range track. 2021-03-03 14:50:02 +01:00
DebaucheryLibrarian
abe56d1207 1.180.3 2021-03-03 14:48:09 +01:00
DebaucheryLibrarian
370f0e784c Allowing input on range track. 2021-03-03 14:48:04 +01:00
DebaucheryLibrarian
780993eb63 Added debug log for actor associations. 2021-03-03 13:56:50 +01:00
DebaucheryLibrarian
9909bbeba5 Extended cup size filter range. 2021-03-03 13:53:18 +01:00
DebaucheryLibrarian
c2779658c6 1.180.2 2021-03-03 13:47:33 +01:00
DebaucheryLibrarian
2f401765b6 Improved cup size query. 2021-03-03 13:47:27 +01:00
DebaucheryLibrarian
0124561686 1.180.1 2021-03-03 04:08:20 +01:00
DebaucheryLibrarian
07bc49604e Improved threeway toggle design. 2021-03-03 04:05:27 +01:00
DebaucheryLibrarian
25ec52f53e 1.180.0 2021-03-03 02:18:44 +01:00
DebaucheryLibrarian
e3b1934653 Added double thumb slider for boob size. 2021-03-03 02:18:36 +01:00
DebaucheryLibrarian
4e6f19d620 1.179.1 2021-03-02 02:30:52 +01:00
DebaucheryLibrarian
408066aba0 Added working minimum cup size slider. 2021-03-02 02:30:44 +01:00
DebaucheryLibrarian
d6bebd8fec Fixed slider radius. 2021-03-01 19:45:29 +01:00
DebaucheryLibrarian
b64c7ddc96 1.179.0 2021-03-01 02:42:12 +01:00
DebaucheryLibrarian
c2ec4c15e3 Added basic filter for actor boob size. 2021-03-01 02:41:53 +01:00
DebaucheryLibrarian
357b0287b2 1.178.0 2021-02-28 03:39:02 +01:00
DebaucheryLibrarian
eca24a7c65 Added WP boob filter to actors page. 2021-02-28 03:38:54 +01:00
DebaucheryLibrarian
46a3906bde 1.177.2 2021-02-27 22:46:59 +01:00
DebaucheryLibrarian
90ca08d8ac Improved release search function to match and concat words instead of escape characters. 2021-02-27 22:46:52 +01:00
DebaucheryLibrarian
8eebcae85d Using direct parent as release actor entity instead of highest parent. 2021-02-27 21:59:33 +01:00
DebaucheryLibrarian
e095d8317b 1.177.1 2021-02-27 18:05:14 +01:00
DebaucheryLibrarian
c2a008afbe Added mimetype check to teasers and trailers. Added chapters to MindGeek scraper, fixed scene ID extraction getting stuck on numbers in domain name. Ordering chapters by timestamp. 2021-02-27 18:05:06 +01:00
DebaucheryLibrarian
a45c5f8f37 Added tags to chapters in API. 2021-02-27 17:19:07 +01:00
DebaucheryLibrarian
e55bc1ef33 Re-added trailer to scene API query. 2021-02-27 04:05:38 +01:00
DebaucheryLibrarian
e60153ad01 Curating chapter media. 2021-02-27 03:57:37 +01:00
DebaucheryLibrarian
cdea877024 1.177.0 2021-02-27 03:52:33 +01:00
DebaucheryLibrarian
2deed3a7eb Added internal GraphQL client, using GraphQL for scenes API. 2021-02-27 03:52:27 +01:00
DebaucheryLibrarian
162e5c2181 1.176.0 2021-02-27 00:39:05 +01:00
DebaucheryLibrarian
bb20659934 Refactored clips into chapters. 2021-02-27 00:37:22 +01:00
DebaucheryLibrarian
0eba0461c9 Added error handler to web server. 2021-02-26 19:39:48 +01:00
DebaucheryLibrarian
f018735052 Added relevance filter to search REST API. 2021-02-26 17:29:02 +01:00
DebaucheryLibrarian
0265ad35c9 1.175.4 2021-02-26 17:22:59 +01:00
DebaucheryLibrarian
4ca6c37cc8 Added relevance to REST release search API, sorting by relevance rank. Improved search result table column naming. 2021-02-26 17:22:54 +01:00
DebaucheryLibrarian
5c028e75a7 1.175.3 2021-02-26 04:31:17 +01:00
DebaucheryLibrarian
34608fe0d7 Added tag photos. 2021-02-26 04:31:08 +01:00
DebaucheryLibrarian
772fef5ff8 Hiding empty search sections. 2021-02-26 03:04:09 +01:00
DebaucheryLibrarian
8641651a55 1.175.2 2021-02-26 02:52:44 +01:00
DebaucheryLibrarian
20129eca5d Fixed Bang scraper. 2021-02-26 02:52:39 +01:00
DebaucheryLibrarian
16a5d92efe 1.175.1 2021-02-26 01:52:33 +01:00
DebaucheryLibrarian
bb9fbc77a9 Removed PG stop word dictionary. Filtering and ordering search results in GraphQL query. 2021-02-26 01:52:28 +01:00
DebaucheryLibrarian
3310236767 1.175.0 2021-02-26 01:33:40 +01:00
DebaucheryLibrarian
1b3bf01ed7 Updated search query function to include ranking. 2021-02-26 01:33:33 +01:00
DebaucheryLibrarian
85372581bd 1.174.2 2021-02-25 15:59:01 +01:00
DebaucheryLibrarian
b4129891dc Improved query curation in release search function. 2021-02-25 15:58:54 +01:00
DebaucheryLibrarian
342d8da29b Fixed wrong rank query. 2021-02-25 03:01:23 +01:00
DebaucheryLibrarian
887bc003e6 1.174.1 2021-02-25 02:56:22 +01:00
DebaucheryLibrarian
2b76dcd15f Fixed search graphql query for S3. 2021-02-25 02:56:16 +01:00
DebaucheryLibrarian
1eb5451060 1.174.0 2021-02-25 02:52:51 +01:00
DebaucheryLibrarian
7818328378 Refactored PG search function to allow superflous words in search query. 2021-02-25 02:52:45 +01:00
DebaucheryLibrarian
3ad1f3d1e3 1.173.6 2021-02-24 13:55:46 +01:00
DebaucheryLibrarian
312c8903e4 Fixed tag scene order. 2021-02-24 13:55:40 +01:00
DebaucheryLibrarian
fe59b23c84 1.173.5 2021-02-24 03:56:31 +01:00
DebaucheryLibrarian
07201c6563 Added S3 field for release actors. 2021-02-24 03:56:20 +01:00
DebaucheryLibrarian
6b2a66cf72 Fixed banner background path. 2021-02-24 03:23:03 +01:00
DebaucheryLibrarian
fe3f820d33 1.173.4 2021-02-24 02:43:39 +01:00
DebaucheryLibrarian
fb2217a733 Preventing actor entry ID from being inserted without entity ID. 2021-02-24 02:43:34 +01:00
DebaucheryLibrarian
af131f903e 1.173.3 2021-02-23 16:47:36 +01:00
DebaucheryLibrarian
736a15958a Fixed poster link in banner. 2021-02-23 16:47:34 +01:00
DebaucheryLibrarian
ff862dbff9 Removed debug log. 2021-02-23 04:10:03 +01:00
DebaucheryLibrarian
fae288633c Catching actor association errors so it does not inhibit media association. 2021-02-23 04:09:33 +01:00
DebaucheryLibrarian
f44cb8bf4c Merge branch 'master' into experimental 2021-02-23 03:36:56 +01:00
DebaucheryLibrarian
c32a5d2930 Added debug log for actor entries. 2021-02-23 03:36:48 +01:00
DebaucheryLibrarian
2775b593ab Updated node-sass and sass-loader. 2021-02-23 02:33:16 +01:00
DebaucheryLibrarian
50627f08b4 Updated sharp. 2021-02-23 02:21:25 +01:00
DebaucheryLibrarian
e6e163b733 1.173.2 2021-02-23 02:08:12 +01:00
DebaucheryLibrarian
39d8b0a17f Replaced eslint-loader with eslint-webpack-plugin. 2021-02-23 02:08:10 +01:00
DebaucheryLibrarian
29765f488b 1.173.1 2021-02-23 01:49:46 +01:00
DebaucheryLibrarian
44c12a0654 Removed TensorFlow from dependencies. 2021-02-23 01:49:43 +01:00
DebaucheryLibrarian
2fa48abb62 1.173.0 2021-02-23 01:31:09 +01:00
DebaucheryLibrarian
2b5aac7633 Fixed S3 display support for movies. 2021-02-23 01:30:38 +01:00
DebaucheryLibrarian
c1829c64c2 Not using media hash subdirs for S3 uploads. Updated video player for S3. 2021-02-23 00:54:19 +01:00
DebaucheryLibrarian
e9603ecec9 Removed S3 address from default config. 2021-02-22 03:16:44 +01:00
DebaucheryLibrarian
37e39dc1ec Added S3 support for media files. Fixed MindGeek scraper for new poster data structure. 2021-02-22 02:33:39 +01:00
DebaucheryLibrarian
9a65d8c0eb Merge branch 'master' into wasabi 2021-02-21 23:00:00 +01:00
DebaucheryLibrarian
c1c58f659d 1.172.3 2021-02-21 22:58:54 +01:00
DebaucheryLibrarian
b79e75349c Fixed indentation. 2021-02-21 22:58:46 +01:00
DebaucheryLibrarian
c0347eed50 1.172.2 2021-02-21 22:17:32 +01:00
DebaucheryLibrarian
8a739893ea Improved duplicate handling. Added tag photos. 2021-02-21 22:17:25 +01:00
DebaucheryLibrarian
a39750d808 Fixed Nubiles deep scrape date. 2021-02-20 01:16:09 +01:00
DebaucheryLibrarian
f310fec869 Added S3 experiment. 2021-02-20 01:12:44 +01:00
DebaucheryLibrarian
39e2abd80a Removed legacy comment. 2021-02-19 00:45:27 +01:00
DebaucheryLibrarian
3ad9a359f4 1.172.1 2021-02-18 04:44:13 +01:00
DebaucheryLibrarian
5e2909c531 Fixed actor unique index preventing multiple actors with the same slug on a different network. Changed scene delete query to be able to handle more scene IDs. 2021-02-18 04:44:04 +01:00
DebaucheryLibrarian
58c01bdfcf 1.172.0 2021-02-17 00:40:27 +01:00
DebaucheryLibrarian
c51cd080fa Improved actor mapping in release associations. Storing alias ID in actor release association. 2021-02-17 00:40:20 +01:00
DebaucheryLibrarian
3469da674a Fixed PornCZ video query. 2021-02-16 19:53:32 +01:00
DebaucheryLibrarian
67055bf920 Improved actor entity and entry ID storage. 2021-02-16 03:37:52 +01:00
DebaucheryLibrarian
b26a029f66 1.171.1 2021-02-14 14:31:38 +01:00
DebaucheryLibrarian
8a7baa02c1 Improved date query for upcoming scenes in ElevatedX scraper. 2021-02-14 14:31:33 +01:00
DebaucheryLibrarian
e39afa8b39 1.171.0 2021-02-14 01:53:10 +01:00
DebaucheryLibrarian
372b5da704 Moved all of ExploitedX and Nebraska Coeds into generic ElevatedX scraper. 2021-02-14 01:53:03 +01:00
DebaucheryLibrarian
f79505f3f6 Added generic ElevatedX scraper. Changed FCUK to ExploitedX network. Testing ElevatedX scraper with ExploitedX network. 2021-02-13 04:49:00 +01:00
DebaucheryLibrarian
4c306effb7 1.170.1 2021-02-13 00:30:53 +01:00
DebaucheryLibrarian
fbbbd99d3d Properly iterating through aliases in actor profile. 2021-02-13 00:30:49 +01:00
DebaucheryLibrarian
24ea7e0c5c 1.170.0 2021-02-11 02:02:04 +01:00
DebaucheryLibrarian
d80dd67ad0 Fixed trailer width in release banner. 2021-02-11 02:01:48 +01:00
DebaucheryLibrarian
a14227b588 Improved animated image handling. 2021-02-11 01:46:11 +01:00
DebaucheryLibrarian
42a2fd8800 Changed Dorcel trailer to teaser. 2021-02-10 23:49:37 +01:00
DebaucheryLibrarian
098d2ef693 1.169.1 2021-02-10 23:46:22 +01:00
DebaucheryLibrarian
9aca5baa2b Added teaser support to Dorcel scraper. 2021-02-10 23:46:15 +01:00
DebaucheryLibrarian
5ba2c0ebd3 1.169.0 2021-02-10 04:21:09 +01:00
DebaucheryLibrarian
23335f8bd9 Added last scrape date to stats page. 2021-02-10 04:20:58 +01:00
DebaucheryLibrarian
b6022a3e90 1.168.10 2021-02-10 03:29:47 +01:00
DebaucheryLibrarian
b6b3def8fa Showing poster in release album. Filtering empty items from album component. 2021-02-10 03:29:41 +01:00
DebaucheryLibrarian
6e2527e5c5 1.168.9 2021-02-10 03:23:57 +01:00
DebaucheryLibrarian
7ff222ce25 Passing recursive parameters to all scraper methods. Using throttle parameters in MindGeek scraper, fixed missing slug breaking scene and actor URLs. 2021-02-10 03:23:48 +01:00
DebaucheryLibrarian
62ad786318 1.168.8 2021-02-10 03:00:23 +01:00
DebaucheryLibrarian
a6c7c60290 Improved movie tile actor overflow. Improved deep movie error feedback. 2021-02-10 03:00:17 +01:00
DebaucheryLibrarian
acc496be47 1.168.7 2021-02-08 04:29:19 +01:00
DebaucheryLibrarian
a04c7dda37 Added log to give insight about media insert failures. 2021-02-08 04:29:12 +01:00
DebaucheryLibrarian
38f53d8de8 1.168.6 2021-02-08 03:35:30 +01:00
DebaucheryLibrarian
dcad0cbe8f Fixed scenes without movies spawning empty movie objects and triggering 'missing entry ID' warning. 2021-02-08 03:35:24 +01:00
DebaucheryLibrarian
fa57575cc3 1.168.5 2021-02-08 02:11:03 +01:00
DebaucheryLibrarian
73b28866ac Scene tile uses first photo if poster is unavailable. 2021-02-08 02:10:56 +01:00
DebaucheryLibrarian
998774fe5c Added tag photos. 2021-02-06 04:15:28 +01:00
DebaucheryLibrarian
0827ced0d7 1.168.4 2021-02-06 01:39:07 +01:00
DebaucheryLibrarian
1cf0e166bb Fixed scene tile thumbnail mobile aspect ratio. Increased release page tag section height to better fit first two tag rows. 2021-02-06 01:39:01 +01:00
DebaucheryLibrarian
df71bfb483 1.168.3 2021-02-05 04:23:19 +01:00
DebaucheryLibrarian
80b8fe3654 Added actor flush, renamed inconsistent actor flush to actor delete. 2021-02-05 04:23:13 +01:00
DebaucheryLibrarian
dbfbd1f04d Fixed movie flush confirmation. 2021-02-05 04:17:47 +01:00
DebaucheryLibrarian
b94a3e05bc Added flush movie argument to index file. 2021-02-05 04:16:59 +01:00
DebaucheryLibrarian
368aa8a89f 1.168.2 2021-02-05 04:14:21 +01:00
DebaucheryLibrarian
80fa953f60 Added movie flush. 2021-02-05 04:14:13 +01:00
DebaucheryLibrarian
a95a159978 Fixed movie scene column. 2021-02-05 04:06:53 +01:00
DebaucheryLibrarian
6be787ecae 1.168.1 2021-02-05 04:05:52 +01:00
DebaucheryLibrarian
7b482e6903 Deleting movie scene associations before deleting scenes. 2021-02-05 04:05:44 +01:00
DebaucheryLibrarian
2db0cab892 1.168.0 2021-02-05 03:50:09 +01:00
DebaucheryLibrarian
457256e731 Added configurable disclaimer. 2021-02-05 03:50:03 +01:00
DebaucheryLibrarian
8aaf060979 1.167.10 2021-02-05 03:37:10 +01:00
DebaucheryLibrarian
688863d16e Catching media storage errors to prevent full crash. 2021-02-05 03:37:04 +01:00
DebaucheryLibrarian
c9b774c498 1.167.9 2021-02-05 03:13:17 +01:00
DebaucheryLibrarian
3e813ca251 Prevent writing to media hasher when hasher stream has closed. 2021-02-05 03:13:10 +01:00
DebaucheryLibrarian
647e9bb186 1.167.8 2021-02-05 03:04:02 +01:00
DebaucheryLibrarian
28a68aa721 Improved scene page layout and spacing. 2021-02-05 03:03:57 +01:00
DebaucheryLibrarian
e0e584658e Changed more tags button design. 2021-02-05 02:49:43 +01:00
DebaucheryLibrarian
980f66fb33 1.167.7 2021-02-05 02:35:10 +01:00
DebaucheryLibrarian
3e0a4406eb Hiding more than 2 rows of tags under expand button on scene page. Fixed album background being bright in dark mode. 2021-02-05 02:35:00 +01:00
DebaucheryLibrarian
5906ed5948 Improved album spacing and sizing. 2021-02-05 02:00:18 +01:00
DebaucheryLibrarian
8902654c4e 1.167.6 2021-02-05 01:54:16 +01:00
DebaucheryLibrarian
15cc970ee2 Added tags to release channels. Labeling DDF Network VR trailers as VR. 2021-02-05 01:54:06 +01:00
DebaucheryLibrarian
8af562e284 Showing courtesy comments on actor album photos. 2021-02-05 01:41:53 +01:00
DebaucheryLibrarian
101f5a1453 1.167.5 2021-02-05 01:29:58 +01:00
DebaucheryLibrarian
4ae130a646 Fixed album navigation interfering with filter navigation. 2021-02-05 01:29:53 +01:00
DebaucheryLibrarian
bd59bdd215 1.167.4 2021-02-04 23:28:15 +01:00
DebaucheryLibrarian
054ea6ac66 Explicitly ordering release photos and covers by stored index. 2021-02-04 23:28:10 +01:00
DebaucheryLibrarian
c5e1f2de2e 1.167.3 2021-02-04 23:09:57 +01:00
DebaucheryLibrarian
4abfcaf9ce Improved vertical image size in album. 2021-02-04 23:09:52 +01:00
DebaucheryLibrarian
0b14f4ab5d 1.167.2 2021-02-04 22:58:23 +01:00
DebaucheryLibrarian
5e12a1e1b1 Fixed not using media limit argument. 2021-02-04 22:58:18 +01:00
DebaucheryLibrarian
456b240df8 1.167.1 2021-02-04 22:55:26 +01:00
DebaucheryLibrarian
f217b161b4 Scraping scene photos from Bang API. 2021-02-04 22:55:19 +01:00
DebaucheryLibrarian
4594dbc763 1.167.0 2021-02-04 03:06:27 +01:00
DebaucheryLibrarian
98cae9270a Added video.js player with VR support for trailers. 2021-02-04 03:06:19 +01:00
DebaucheryLibrarian
91ba916884 1.166.1 2021-02-04 01:18:52 +01:00
DebaucheryLibrarian
315bf0fc89 Fixed missing date limit default argument. 2021-02-04 01:18:46 +01:00
DebaucheryLibrarian
733d17ae7a 1.166.0 2021-02-04 01:13:11 +01:00
DebaucheryLibrarian
ff123b99b7 Added WankzVR update, scene and profile scraper. 2021-02-04 01:13:02 +01:00
DebaucheryLibrarian
0b99e72924 Added Kink VR to Kink network using BaDoink scraper. 2021-02-03 21:29:56 +01:00
DebaucheryLibrarian
2da5939648 1.165.0 2021-02-03 21:03:41 +01:00
DebaucheryLibrarian
79b51eca67 Added BaDoink profile scraper. Improved convert wrapper. 2021-02-03 21:03:35 +01:00
DebaucheryLibrarian
cd417f40a8 Fixed VR tags in seed, fixed Honour May LP studio name and slug mixup. 2021-02-03 20:11:28 +01:00
DebaucheryLibrarian
85d42dec03 1.164.1 2021-02-03 20:00:48 +01:00
DebaucheryLibrarian
8dabef57b6 Added missing LegalPorno studios with prefix codes. 2021-02-03 20:00:42 +01:00
DebaucheryLibrarian
2a51fc82fd 1.164.0 2021-02-03 19:21:53 +01:00
DebaucheryLibrarian
11ad5f8bad Using navigation for toggling album. Using album for tag photos. Fixed portrait albums. 2021-02-03 19:21:47 +01:00
DebaucheryLibrarian
e3dc989798 1.163.1 2021-02-03 03:22:52 +01:00
DebaucheryLibrarian
8ea3fccb61 Added VR tag photos. 2021-02-03 03:22:43 +01:00
DebaucheryLibrarian
97cfca74ad 1.163.0 2021-02-03 02:59:44 +01:00
DebaucheryLibrarian
0c2e45141d Added BaDoink latest and scene scraper. 2021-02-03 02:59:39 +01:00
DebaucheryLibrarian
824fb9ef37 Changed profile network argument to context. 2021-02-03 00:50:00 +01:00
DebaucheryLibrarian
a19f235684 1.162.1 2021-02-03 00:47:06 +01:00
DebaucheryLibrarian
6d93083581 Removed superfluous MindGeek scrapers. 2021-02-03 00:46:59 +01:00
DebaucheryLibrarian
8337ce8dbd Scrolling release page up when browsing to movie. 2021-02-02 22:45:10 +01:00
DebaucheryLibrarian
92ba264cff 1.162.0 2021-02-02 22:36:54 +01:00
DebaucheryLibrarian
4db9a34a34 Removed superfluous Gamma scrapers. 2021-02-02 22:36:47 +01:00
DebaucheryLibrarian
251c3964bd 1.161.8 2021-02-02 04:08:57 +01:00
DebaucheryLibrarian
1e43c0e4c3 Showing movie cover as banner background. 2021-02-02 04:08:52 +01:00
DebaucheryLibrarian
696eb9a9d0 1.161.7 2021-02-02 04:03:41 +01:00
DebaucheryLibrarian
45badad8f8 Hiding movies without date. 2021-02-02 04:03:36 +01:00
DebaucheryLibrarian
2bd02a80a6 1.161.6 2021-02-02 04:00:06 +01:00
DebaucheryLibrarian
133aa065fb Fixed missing movie batch ID. Improved release page spacing. 2021-02-02 04:00:01 +01:00
DebaucheryLibrarian
b856f81148 1.161.5 2021-02-02 03:52:20 +01:00
DebaucheryLibrarian
5988e9b1a8 Fixed movie GraphQL to include logo status. 2021-02-02 03:52:15 +01:00
DebaucheryLibrarian
0b7d7e0602 1.161.4 2021-02-02 03:47:15 +01:00
DebaucheryLibrarian
dc331637a5 Filtering empty results in release curation. 2021-02-02 03:47:06 +01:00
DebaucheryLibrarian
3bbc5a5e87 Hard merging covers to preserve order. 2021-02-02 03:10:58 +01:00
DebaucheryLibrarian
55cadcfe82 1.161.3 2021-02-02 02:18:18 +01:00
DebaucheryLibrarian
c1124abde0 Improved scene poster selector in Woodman scraper. 2021-02-02 02:18:12 +01:00
DebaucheryLibrarian
fddbafc2d5 1.161.2 2021-02-02 02:11:22 +01:00
DebaucheryLibrarian
63f43013c3 Removed superfluous grandparent from entity query. 2021-02-02 02:11:16 +01:00
DebaucheryLibrarian
e4e0eb23dd Removed unnecessary depth calculation from entity query. 2021-02-02 01:59:51 +01:00
DebaucheryLibrarian
89a729924d 1.161.1 2021-02-02 01:51:30 +01:00
DebaucheryLibrarian
6364912aa8 Added children to deep release entity, removed database dependency from Perfect Gonzo scraper. 2021-02-02 01:51:22 +01:00
DebaucheryLibrarian
ab83dd2e55 1.161.0 2021-02-02 01:31:24 +01:00
DebaucheryLibrarian
d5cdfb36a9 Selecting included networks with infinite parent depth to facilitate scraper resolve. 2021-02-02 01:31:12 +01:00
DebaucheryLibrarian
46c0b269c3 1.160.4 2021-02-01 20:49:13 +01:00
DebaucheryLibrarian
4b5cd50122 Fixed slug lookup in Perfect Gonzo scraper. 2021-02-01 20:49:08 +01:00
DebaucheryLibrarian
aade7490f8 Querying infinite parent depth for deep release entities. 2021-02-01 01:45:30 +01:00
DebaucheryLibrarian
97c088cfb4 Added Anal Only and upcoming scraping to Mike Adriano. Fixed profile expand arrow color. 2021-01-30 17:43:33 +01:00
DebaucheryLibrarian
bfb5006e95 Added actor scene URL parameter to Gamma scraper to phase out release URL function. 2021-01-30 01:12:42 +01:00
DebaucheryLibrarian
d3d08b9c21 1.160.3 2021-01-30 00:01:46 +01:00
DebaucheryLibrarian
9535ab7953 Regarding layout parameters from parent and grandparent entities. Removed Evil Angel and Fantasy Massage scraper wrappers. 2021-01-30 00:01:40 +01:00
DebaucheryLibrarian
b42bdc1d3e 1.160.2 2021-01-29 15:37:00 +01:00
DebaucheryLibrarian
89956f3ad5 Increased album tile size. 2021-01-29 15:36:54 +01:00
DebaucheryLibrarian
b58ceb85bc 1.160.1 2021-01-29 04:26:52 +01:00
DebaucheryLibrarian
6938e88fbf Fixed some Gamma scene scrapers. 2021-01-29 04:26:45 +01:00
DebaucheryLibrarian
1fc67704dc 1.160.0 2021-01-29 02:38:20 +01:00
DebaucheryLibrarian
b7aaeada45 Improved movie scraping. 2021-01-29 02:38:05 +01:00
DebaucheryLibrarian
4d89256a4c Merging improvements. 2021-01-27 00:21:58 +01:00
DebaucheryLibrarian
7185c8dc08 1.159.10 2021-01-25 23:54:06 +01:00
DebaucheryLibrarian
b506a00e7d Improved scene merging. Improved Porn World/DDF scraper for poster and title redundancy. Fixed SFW poster showing in NSFW mode. 2021-01-25 23:53:56 +01:00
DebaucheryLibrarian
5a975ad0bf 1.159.9 2021-01-25 23:25:14 +01:00
DebaucheryLibrarian
b80eca35d8 Fixed Gamma scraper breaking when scene has no movie. Improved missing thumbnail presentation on mobile scene tiles. 2021-01-25 23:24:51 +01:00
DebaucheryLibrarian
dba99a4170 Showing cover or first photo as movie trailer poster. 2021-01-25 23:15:41 +01:00
DebaucheryLibrarian
5547ff7e76 1.159.8 2021-01-25 23:01:16 +01:00
DebaucheryLibrarian
dc98fcad5a Scraping Gamma movies. Changed movie detail bar position, and scene detail bar mobile spacing. 2021-01-25 23:01:07 +01:00
DebaucheryLibrarian
41259eae5d 1.159.7 2021-01-25 00:30:29 +01:00
DebaucheryLibrarian
d3703d81b7 Added Woodman logos. 2021-01-25 00:30:19 +01:00
DebaucheryLibrarian
24178e7b04 1.159.6 2021-01-25 00:11:17 +01:00
DebaucheryLibrarian
486dbc5613 Improved Woodman scraper, added profiles. 2021-01-25 00:10:57 +01:00
DebaucheryLibrarian
e9cbf5dab2 Added Woodman Casting X and WUNF update and scene scraper. Fixed actor and fallback media arrays being merged in deep scraper. 2021-01-24 23:31:28 +01:00
DebaucheryLibrarian
b719a166d2 1.159.5 2021-01-24 20:43:40 +01:00
DebaucheryLibrarian
297f79f6e2 Restored sorting trailers by quality. 2021-01-24 20:43:34 +01:00
DebaucheryLibrarian
af59ad3d33 Fixed dark Private logos. 2021-01-24 17:44:34 +01:00
DebaucheryLibrarian
3573b84c97 Removed protocol from Vixen trailer proxy config. 2021-01-24 17:33:42 +01:00
DebaucheryLibrarian
e105d665ae Added Vixen trailer CDN URLs to default proxy config. Improved album photo sizes. 2021-01-24 17:31:32 +01:00
DebaucheryLibrarian
714f70c9ce 1.159.4 2021-01-24 17:17:03 +01:00
DebaucheryLibrarian
4151412156 Fixed Vixen trailer scraping. Using album instead of expand for actor photos. 2021-01-24 17:16:55 +01:00
DebaucheryLibrarian
f7f9862489 Improved album width on narrow screens. 2021-01-23 23:56:24 +01:00
DebaucheryLibrarian
7e52f6d18d Fixed album title overflow. 2021-01-23 23:50:15 +01:00
DebaucheryLibrarian
e1b52de7a3 1.159.3 2021-01-23 23:27:02 +01:00
DebaucheryLibrarian
0a0a3ddd7b Improved appearence of empty scene media banner. 2021-01-23 23:26:56 +01:00
DebaucheryLibrarian
fdb48f0d6d 1.159.2 2021-01-23 23:09:12 +01:00
DebaucheryLibrarian
2f3eb0e16c Improved album layout. 2021-01-23 23:09:05 +01:00
DebaucheryLibrarian
18a1d74a9a 1.159.1 2021-01-23 23:03:33 +01:00
DebaucheryLibrarian
0f29151200 Fixed release banner, improved album layout. 2021-01-23 23:03:21 +01:00
DebaucheryLibrarian
0a7378feb4 1.159.0 2021-01-23 01:18:31 +01:00
DebaucheryLibrarian
59ba84b7b1 Added album button and component to scene page. 2021-01-23 01:18:20 +01:00
DebaucheryLibrarian
985b523031 Scraping movie links from Gamma scene page. Removed expand option from scene page media to make way for album button. 2021-01-22 22:55:20 +01:00
DebaucheryLibrarian
40e0c92ec7 1.158.9 2021-01-22 15:40:54 +01:00
DebaucheryLibrarian
bd9b795516 Fixed avatars without entropy being discarded. 2021-01-22 15:40:49 +01:00
DebaucheryLibrarian
130368ca70 Fixed tile favicon. 2021-01-22 00:37:16 +01:00
DebaucheryLibrarian
611a2d66a0 1.158.8 2021-01-22 00:26:14 +01:00
DebaucheryLibrarian
b16cc26024 Added Zero Tolerance channel to network. 2021-01-22 00:26:06 +01:00
DebaucheryLibrarian
3babb9ee68 Fixed new label padding. 2021-01-19 16:37:33 +01:00
DebaucheryLibrarian
9388eb5993 Fixed new label CSS. 2021-01-19 16:36:17 +01:00
DebaucheryLibrarian
0b1f5c06bc Fixed new label padding. 2021-01-19 16:34:57 +01:00
DebaucheryLibrarian
1c1b933438 1.158.7 2021-01-19 16:33:41 +01:00
DebaucheryLibrarian
0663109634 Fixed new label positioning. 2021-01-19 16:33:35 +01:00
DebaucheryLibrarian
c329ffdf07 1.158.6 2021-01-19 16:31:07 +01:00
DebaucheryLibrarian
b89fe1805f Moved mobile scene tile details bar to top. Minor scene tile improvements. 2021-01-19 16:31:02 +01:00
DebaucheryLibrarian
ca8429150e 1.158.5 2021-01-19 15:58:35 +01:00
DebaucheryLibrarian
a95e409366 Fixed Kink deep scrape photos. Fixed favicon ratio in compact scene tile. Hiding scroll buttons on small screens. 2021-01-19 15:58:27 +01:00
DebaucheryLibrarian
f39270ee91 1.158.4 2021-01-18 15:27:13 +01:00
DebaucheryLibrarian
9debb1776b Fixed favicon width. 2021-01-18 15:27:07 +01:00
DebaucheryLibrarian
8183525961 1.158.3 2021-01-18 00:49:06 +01:00
DebaucheryLibrarian
6e8620fbbb Slimmed down scene tile details in compact mode. Hiding link icon in tile details when no link is available. 2021-01-18 00:49:00 +01:00
DebaucheryLibrarian
5c8a6b3a70 1.158.2 2021-01-18 00:33:00 +01:00
DebaucheryLibrarian
46173cb6c3 Reduced tile grid padding in compact mode. 2021-01-18 00:32:52 +01:00
DebaucheryLibrarian
bebf814577 Adjusted new label for compact compatability. 2021-01-18 00:31:52 +01:00
DebaucheryLibrarian
7d1fb86e63 1.158.1 2021-01-17 23:43:03 +01:00
DebaucheryLibrarian
f9c6e6f0dc Improved header spacing. 2021-01-17 23:43:00 +01:00
DebaucheryLibrarian
1238f90268 1.158.0 2021-01-17 23:31:57 +01:00
DebaucheryLibrarian
1dea94c0cf Improved compact scene tile layout, added dark versions of entity favicons. 2021-01-17 23:31:49 +01:00
DebaucheryLibrarian
dd5284c55a Fixed scroll to top. 2021-01-17 21:24:20 +01:00
DebaucheryLibrarian
8bc1fbf530 1.157.0 2021-01-17 02:07:16 +01:00
DebaucheryLibrarian
48f247a919 Changed scene media grid layout. 2021-01-17 02:07:02 +01:00
DebaucheryLibrarian
251bb9476d Added Gaywire, modified Bang Bros scraper to accomodate. 2021-01-17 01:43:55 +01:00
DebaucheryLibrarian
8387f676fc Added Top Web Models to profile config. 2021-01-16 04:12:22 +01:00
DebaucheryLibrarian
e1aa48f3c1 1.156.1 2021-01-16 04:10:57 +01:00
DebaucheryLibrarian
e3ef0a0d69 Added Top Web Models profile scraper. 2021-01-16 04:10:43 +01:00
DebaucheryLibrarian
b9e4764516 Fixed Pascals Sub Sluts interpreting metric as imperial height, filtering unlikely in interpolation. Splitting double actor entries in Top Web Models. 2021-01-15 16:14:48 +01:00
DebaucheryLibrarian
7e78a39717 1.156.0 2021-01-15 04:06:02 +01:00
DebaucheryLibrarian
b8df8e6507 Added Top Web Models update and scene scraper. 2021-01-15 04:04:32 +01:00
DebaucheryLibrarian
451ffdc48b Added Top Web Models directory. 2021-01-14 02:10:35 +01:00
DebaucheryLibrarian
4bbd9e8120 1.155.0 2021-01-14 02:08:39 +01:00
DebaucheryLibrarian
12c3f0d7b9 Added Top Web Models framework. 2021-01-14 01:37:50 +01:00
DebaucheryLibrarian
dbdf0fcc0b Updated README. 2021-01-13 21:29:47 +01:00
DebaucheryLibrarian
422b6c4252 1.154.0 2021-01-13 21:29:11 +01:00
DebaucheryLibrarian
39d149c728 Added default deep scrape fetch method. Added Karups scene and profile scraper. Added schoolgirl tag photo. 2021-01-13 21:29:05 +01:00
DebaucheryLibrarian
cc64f2911f 1.153.4 2021-01-13 16:19:59 +01:00
DebaucheryLibrarian
6fb8c77846 Updated README. 2021-01-13 16:19:52 +01:00
DebaucheryLibrarian
2bae6f693e Removed stray console log. 2021-01-13 16:08:53 +01:00
DebaucheryLibrarian
8d8cdcd219 1.153.3 2021-01-13 16:08:25 +01:00
DebaucheryLibrarian
ae0efccb04 Skipping Babel, updated node version. Improved deep scrape array merge. 2021-01-13 16:08:19 +01:00
DebaucheryLibrarian
ef1d34e4de 1.153.2 2021-01-13 15:43:59 +01:00
DebaucheryLibrarian
cdd4220cb9 Fixed release actor crash when no actors are present. 2021-01-13 15:43:53 +01:00
DebaucheryLibrarian
bf814c0b9d 1.153.1 2021-01-13 15:38:26 +01:00
DebaucheryLibrarian
f95c80f73f Using poster photo for Pinky XXX. 2021-01-13 01:10:29 +01:00
DebaucheryLibrarian
5d3e3b6cee 1.153.0 2021-01-13 00:52:17 +01:00
DebaucheryLibrarian
52356f0f31 Added Pinky XXX, changed ethnicity tags. 2021-01-13 00:52:05 +01:00
DebaucheryLibrarian
68aacb498a 1.152.7 2021-01-11 23:31:39 +01:00
DebaucheryLibrarian
ad73c11cb4 Using date and title for Nubiles entry ID. 2021-01-11 23:31:33 +01:00
DebaucheryLibrarian
e65828c729 1.152.6 2021-01-11 16:20:09 +01:00
DebaucheryLibrarian
db4e74fb99 Fixed Nubiles base poster query, handling trailing commas in qu source set. Added profile scene scraper to Dogfart. Added tag photo. 2021-01-11 16:20:01 +01:00
DebaucheryLibrarian
e38922f372 Removed redundant sitename from MindGeek session error. 2021-01-05 16:35:49 +01:00
DebaucheryLibrarian
e1d6c9e489 Added site name to MindGeek session error. 2021-01-05 16:34:32 +01:00
DebaucheryLibrarian
d2d124cccf 1.152.5 2021-01-05 16:27:26 +01:00
DebaucheryLibrarian
9ca2ec6dd0 Fixed parent entity relations in seed file. Fixed MindGeek scraper session URL determination. 2021-01-05 16:27:20 +01:00
DebaucheryLibrarian
fd92e7e260 1.152.4 2021-01-05 03:29:10 +01:00
DebaucheryLibrarian
150988ecb9 Fixed filter tooltips closing on click. Added tag photo. 2021-01-05 03:29:02 +01:00
DebaucheryLibrarian
bd3e1a0bde 1.152.3 2021-01-04 19:58:01 +01:00
DebaucheryLibrarian
b791aaca5a Improved consent warning layout. 2021-01-04 19:57:53 +01:00
DebaucheryLibrarian
a5eef66a1c 1.152.2 2021-01-04 01:46:11 +01:00
DebaucheryLibrarian
df5a9c9fd9 Using vertical button layout in consent warning on small screens. 2021-01-04 01:46:05 +01:00
DebaucheryLibrarian
be15e360c1 Darkened dialog background. 2021-01-04 01:39:55 +01:00
DebaucheryLibrarian
ca6478ca88 1.152.1 2021-01-04 01:30:47 +01:00
DebaucheryLibrarian
62ef041b35 Added filter presets to consent warning. Updating scenes when tag filter changes. 2021-01-04 01:30:39 +01:00
DebaucheryLibrarian
ab83a42dfb 1.152.0 2021-01-03 23:32:18 +01:00
DebaucheryLibrarian
cb4b5ce640 Added filter dialog toggle to sidebar. Moved filter dialog to container. Using events to toggle sidebar from header. 2021-01-03 23:32:09 +01:00
DebaucheryLibrarian
7bbb2f3557 Added tag filter dialog. 2021-01-03 22:53:51 +01:00
DebaucheryLibrarian
f27af19670 Added tag photo. 2021-01-03 16:36:47 +01:00
DebaucheryLibrarian
c59f05a2f8 1.151.3 2021-01-02 03:20:45 +01:00
DebaucheryLibrarian
8739ec08cf Fixed qu init selector. Fixed Aziani scene page scope. 2021-01-02 03:20:39 +01:00
DebaucheryLibrarian
70795a69c8 1.151.2 2021-01-01 04:28:17 +01:00
DebaucheryLibrarian
236d4fcde7 Fixed scene media lazy image. Fixed Perv City actor scope. 2021-01-01 04:28:10 +01:00
DebaucheryLibrarian
ba3b87471e Added scene flush. Added temporary media insert log for integer out of range error. 2020-12-30 04:17:09 +01:00
DebaucheryLibrarian
91746c73e1 1.151.1 2020-12-30 03:39:47 +01:00
DebaucheryLibrarian
f0a90db912 Only allow actor flush by name for non-entity specific actors. 2020-12-30 03:39:40 +01:00
DebaucheryLibrarian
4fd262dc60 1.151.0 2020-12-30 03:19:16 +01:00
DebaucheryLibrarian
8aabcd6443 Added actor flush. 2020-12-30 03:19:09 +01:00
DebaucheryLibrarian
af67d733ad Added profile flush. 2020-12-30 02:23:43 +01:00
DebaucheryLibrarian
cdc963c42c 1.150.1 2020-12-30 00:19:15 +01:00
DebaucheryLibrarian
e807f049d8 Showing actor photos without entropy value. 2020-12-30 00:19:08 +01:00
DebaucheryLibrarian
a51a159886 1.150.0 2020-12-30 00:16:12 +01:00
DebaucheryLibrarian
770e5b75a5 Added profile interpolate command line argument. 2020-12-30 00:16:05 +01:00
DebaucheryLibrarian
71e76e359a 1.149.9 2020-12-29 23:44:44 +01:00
DebaucheryLibrarian
a8f68f4993 Fixed boolean handling in actor profile curation. 2020-12-29 23:44:38 +01:00
DebaucheryLibrarian
92eed64fe8 1.149.8 2020-12-29 20:04:24 +01:00
DebaucheryLibrarian
13791be485 Updated sidebar transition to Vue v3. 2020-12-29 20:04:18 +01:00
DebaucheryLibrarian
ba8a3036a5 Fixed tag photo scroll and lazy loading. 2020-12-29 20:02:26 +01:00
DebaucheryLibrarian
b6bf043c48 1.149.7 2020-12-29 18:54:02 +01:00
DebaucheryLibrarian
361e8f1bd3 Fixed Kelly Madison title regex. 2020-12-29 18:53:55 +01:00
DebaucheryLibrarian
c7cb0e439d 1.149.6 2020-12-29 04:22:08 +01:00
DebaucheryLibrarian
0f052a0631 Fixed actor boolean logic, addressing missing boob, tattoo and piercing info. Removed scroll background, fixed actor photo load event and padding. 2020-12-29 04:20:33 +01:00
DebaucheryLibrarian
0f88ae324e 1.149.5 2020-12-29 02:05:29 +01:00
DebaucheryLibrarian
bc944c2373 Restored Mike Adriano scraper. 2020-12-29 02:05:22 +01:00
DebaucheryLibrarian
5476597343 Fixed tag filter tooltip. Added emits property to tooltip component. 2020-12-29 00:51:59 +01:00
DebaucheryLibrarian
8dd10f7e77 Adjusting tooltip arrow position, added open and close events. Fixed search tooltip layout. 2020-12-29 00:42:02 +01:00
DebaucheryLibrarian
442e69187b 1.149.4 2020-12-28 01:33:15 +01:00
DebaucheryLibrarian
029099d4a5 Added fallback createdAt date to date range. 2020-12-28 01:33:09 +01:00
DebaucheryLibrarian
1ec2b3ac36 1.149.3 2020-12-28 01:29:41 +01:00
DebaucheryLibrarian
6baa6b0802 Fixed fallback create dates in scene tiles. Fixed Mike Adriano entryIds and trailers for Nympho. 2020-12-28 01:29:34 +01:00
DebaucheryLibrarian
679e09f27e Changed scroll component button design. Removed unnecessary z-index from tooltip arrow. 2020-12-28 00:02:18 +01:00
DebaucheryLibrarian
b4f6373605 1.149.2 2020-12-27 23:43:15 +01:00
DebaucheryLibrarian
77ec2d3747 Using advanced merge library to maximize scraped data. 2020-12-27 23:43:08 +01:00
DebaucheryLibrarian
9c926a1d81 Fixed Mike Adriano dates for scene pages. 2020-12-27 23:36:15 +01:00
DebaucheryLibrarian
31af1ca9e6 Fixed movie tile lazy loading and SFW image. 2020-12-27 23:10:11 +01:00
DebaucheryLibrarian
1d1c9eae83 1.149.1 2020-12-27 22:45:42 +01:00
DebaucheryLibrarian
ded414577f Fixed default actor avatar allocation. Fixed lazy loading in actor photos component. 2020-12-27 22:45:38 +01:00
DebaucheryLibrarian
f58c07137a 1.149.0 2020-12-27 04:21:36 +01:00
DebaucheryLibrarian
a7e6f470f7 Improved tooltip behavior and styling. 2020-12-27 04:21:10 +01:00
DebaucheryLibrarian
229d74d266 Using teleport for tooltips. Moved theme class to body tag with UI observer. 2020-12-27 02:15:06 +01:00
DebaucheryLibrarian
12f247a927 Fixed double anchor tags in header and sidebar nav. 2020-12-27 00:40:35 +01:00
DebaucheryLibrarian
2e95e1e32b Passing router as reactive object to store, so values are automatically unwrapped. 2020-12-27 00:32:42 +01:00
DebaucheryLibrarian
c503e12adb Fixed scroll component so it uses slot props instead of the depcrecated . 2020-12-26 23:51:27 +01:00
DebaucheryLibrarian
ced8f447a7 Added tooltip menu with header toggles and upcoming filter access. 2020-12-20 23:20:41 +01:00
DebaucheryLibrarian
d50cfb8dd6 1.148.2 2020-12-20 20:16:11 +01:00
DebaucheryLibrarian
c1838d4390 Replaced consent warning backdrop blur with darker background, as backdrop blur is not supported in Firefox. 2020-12-20 20:16:04 +01:00
DebaucheryLibrarian
a127dfb8af 1.148.1 2020-12-20 19:50:07 +01:00
DebaucheryLibrarian
27e5583849 Using generic session ID variable for to determine consent warning, rather than dedicated property. 2020-12-20 19:49:57 +01:00
DebaucheryLibrarian
4a1faa0074 1.148.0 2020-12-20 04:21:35 +01:00
DebaucheryLibrarian
5f4039c5d4 Added sharpness and re-added entropy to avatars, ignoring low-entropy photos as main avatar and in profile photo list. 2020-12-20 04:21:28 +01:00
DebaucheryLibrarian
cbcac0725d 1.147.2 2020-12-19 23:01:24 +01:00
DebaucheryLibrarian
bd77d4347d Tied consent warning to session. 2020-12-19 23:01:17 +01:00
DebaucheryLibrarian
be4d025505 Fixed trailer path regex for some Hush sites. 2020-12-19 02:35:41 +01:00
DebaucheryLibrarian
17c9499ec4 1.147.1 2020-12-19 01:03:23 +01:00
DebaucheryLibrarian
67ed249239 Added poster to API scene overview. 2020-12-19 01:03:15 +01:00
DebaucheryLibrarian
2b808025f9 1.147.0 2020-12-19 00:40:44 +01:00
DebaucheryLibrarian
0d41fb48dc Removed outdated movie releases from API. Added API endpoint for scene posters. 2020-12-19 00:40:36 +01:00
DebaucheryLibrarian
7bc4a955ba 1.146.6 2020-12-19 00:19:45 +01:00
DebaucheryLibrarian
4ccd8bf07a Fixed missing initial value in scraper reduce, breaking first network (21 Naturals). 2020-12-19 00:19:38 +01:00
DebaucheryLibrarian
ddf0958c04 1.146.5 2020-12-18 23:50:19 +01:00
DebaucheryLibrarian
78aa1ed724 Updated warning message. 2020-12-18 23:50:13 +01:00
DebaucheryLibrarian
0215216e34 1.146.4 2020-12-18 23:41:35 +01:00
DebaucheryLibrarian
02dc74e395 Updated warning message. 2020-12-18 23:41:28 +01:00
DebaucheryLibrarian
b8b58726ac 1.146.3 2020-12-18 23:32:52 +01:00
DebaucheryLibrarian
9853bce90b Fixed logo size in warning. 2020-12-18 23:32:47 +01:00
DebaucheryLibrarian
116ba9c6d2 1.146.2 2020-12-18 23:28:09 +01:00
DebaucheryLibrarian
83f51219e4 Updated warning message. Added grandparent networks to network seed file, merged 21Naturals and 21Sextreme into 21Sextury. 2020-12-18 23:28:02 +01:00
DebaucheryLibrarian
5629190bf1 1.146.1 2020-12-18 04:08:38 +01:00
DebaucheryLibrarian
112970050c Updated tag photos. 2020-12-18 04:08:28 +01:00
DebaucheryLibrarian
2e3a3fd53e 1.146.0 2020-12-18 02:10:36 +01:00
DebaucheryLibrarian
5b16941ec5 Added content warning dialog. 2020-12-18 02:10:30 +01:00
DebaucheryLibrarian
5e7741afe8 1.145.6 2020-12-17 03:48:43 +01:00
DebaucheryLibrarian
aa8f1bb6be Fixed age query for release details. 2020-12-17 03:48:38 +01:00
DebaucheryLibrarian
03915110cf 1.145.5 2020-12-17 03:43:18 +01:00
DebaucheryLibrarian
d67dca60fc Fixed birthdate interpolation and query. 2020-12-17 03:43:09 +01:00
DebaucheryLibrarian
a3306ad4e7 1.145.4 2020-12-17 02:05:07 +01:00
DebaucheryLibrarian
cd8e810c35 Fixed various Kelly Madison scraper issues. 2020-12-17 02:05:01 +01:00
DebaucheryLibrarian
d0f8e21466 1.145.3 2020-12-16 03:53:53 +01:00
DebaucheryLibrarian
9413bd5357 Updated tag photos. 2020-12-16 03:53:44 +01:00
DebaucheryLibrarian
e6de8f0f9a 1.145.2 2020-12-16 00:51:07 +01:00
DebaucheryLibrarian
cc83b832f1 Attach base actor to scenes scraped from profile. 2020-12-16 00:50:58 +01:00
DebaucheryLibrarian
6ed02933a6 Added tag photos. 2020-12-15 04:45:04 +01:00
DebaucheryLibrarian
61b8f62221 1.145.1 2020-12-14 03:16:54 +01:00
DebaucheryLibrarian
07f0249717 Upgraded pg, knex and postgraphile. Updated nvmrc version to latest node LTS. Fixed Gamma scraper not passing request headers to new http module. 2020-12-14 03:16:47 +01:00
DebaucheryLibrarian
80b0f9ee0f 1.145.0 2020-12-05 02:44:54 +01:00
DebaucheryLibrarian
71196688ae Removed console log. 2020-12-05 02:44:39 +01:00
DebaucheryLibrarian
f0bec85ef8 Added Fuck'n'Drive and Jizz On Teens latest layouts to First Anal Quest scraper. 2020-12-05 02:24:31 +01:00
DebaucheryLibrarian
2e0fba3de9 Added First Anal Quest and Double View Casting profile scrapers. 2020-12-04 23:53:20 +01:00
DebaucheryLibrarian
be1821b9eb Changed --inspect to --report to avoid conflict with Node's own debug tools. 2020-12-02 21:26:55 +01:00
DebaucheryLibrarian
f5939c81d3 1.144.0 2020-12-02 03:17:42 +01:00
DebaucheryLibrarian
2656e3adb0 Added First Anal Quest and Double View Casting latest and scene scraper. 2020-12-02 03:17:32 +01:00
DebaucheryLibrarian
bfbfa761ef 1.143.2 2020-12-01 23:00:24 +01:00
DebaucheryLibrarian
6f19a8a642 Using ageFromBirth for age on scene date. Using infinite depth and colors for inspect. 2020-12-01 23:00:14 +01:00
DebaucheryLibrarian
bfbd2ddc00 1.143.1 2020-11-30 03:09:37 +01:00
DebaucheryLibrarian
eaa40190cc Fixed entity link in search results. 2020-11-30 03:09:31 +01:00
DebaucheryLibrarian
2758b90019 1.143.0 2020-11-29 04:00:12 +01:00
DebaucheryLibrarian
9a61d2305c Added fixed actor age. Added male profiles to Littlr Caprice Dreams scraper. Added various tag photos. 2020-11-29 03:59:47 +01:00
DebaucheryLibrarian
71c884fe48 Improved Little Caprice Dreams scraper. 2020-11-28 00:46:30 +01:00
DebaucheryLibrarian
9a183c7ffb Added channel matching to Little Caprice Dreams. 2020-11-27 03:23:12 +01:00
DebaucheryLibrarian
60485751e2 Added support for upcoming scenes to Bang scraper. 2020-11-26 22:07:54 +01:00
DebaucheryLibrarian
4e559f63e3 Including all children of included networks, separated included children into dedicated property. 2020-11-26 04:26:52 +01:00
DebaucheryLibrarian
1b407254a7 Increased hard limit in release API. 2020-11-26 04:01:01 +01:00
DebaucheryLibrarian
0b86def315 Ignoring non-scene page (possible CF or similar protection) in Vixen scraper. 2020-11-26 03:27:21 +01:00
DebaucheryLibrarian
6633ce78d0 Returning empty array instead of null from empty Vixen page. 2020-11-26 03:14:32 +01:00
DebaucheryLibrarian
54df9d0c78 Fixed empty page breaking Vixen scraper. 2020-11-26 03:13:43 +01:00
DebaucheryLibrarian
980efbc93d Added series as channels with logos and photo album scraping to Little Caprice. Added various tag photos. 2020-11-24 04:29:44 +01:00
DebaucheryLibrarian
711a9441a6 Added Little Caprice Dreams scraped (WIP). 2020-11-23 04:32:56 +01:00
DebaucheryLibrarian
eae9ee3cbe 1.142.2 2020-11-23 00:38:30 +01:00
DebaucheryLibrarian
8688c28d0f Replaced queueMethod in media sources for new interval/concurrency options. 2020-11-23 00:38:22 +01:00
DebaucheryLibrarian
aa1bba84aa 1.142.1 2020-11-23 00:30:28 +01:00
DebaucheryLibrarian
ca7c8f0afd Updated place resolve module to use new HTTP module. 2020-11-23 00:30:20 +01:00
DebaucheryLibrarian
d4c5da2a76 1.142.0 2020-11-23 00:05:16 +01:00
DebaucheryLibrarian
0633197793 Removed direct bhttp usage from scrapers in favor of local http module. Deleted legacy scrapers, as old code is available via git repo history. 2020-11-23 00:05:02 +01:00
DebaucheryLibrarian
3d427f7e1d Allowing HTTP rate limits to be set by configuration or argument. 2020-11-22 23:50:24 +01:00
DebaucheryLibrarian
6a5063cf32 Fixed PornCZ scene photos attribute. 2020-11-22 04:13:21 +01:00
DebaucheryLibrarian
081a5a1e8c Updated HTTP call in Gamma scraper. 2020-11-22 04:09:44 +01:00
DebaucheryLibrarian
b9b777c621 Using new HTTP module with a dynamic rate limiter. 2020-11-22 04:07:09 +01:00
DebaucheryLibrarian
5d0fe44130 1.141.2 2020-11-19 02:01:24 +01:00
DebaucheryLibrarian
77f9193669 Updated Dorcel scraper, added movie support. 2020-11-19 02:01:13 +01:00
DebaucheryLibrarian
ecc90be12c 1.141.1 2020-11-15 23:50:11 +01:00
DebaucheryLibrarian
d14ef90136 Scraping actor scenes from Hussie Pass. Adding entity to actor base releases. 2020-11-15 23:50:04 +01:00
DebaucheryLibrarian
b952b758d7 1.141.0 2020-11-15 04:33:40 +01:00
DebaucheryLibrarian
df9a6eac05 Updated Hussie Pass scraper for new site design. Added cock size to profiles. 2020-11-15 04:33:24 +01:00
DebaucheryLibrarian
b3a5d7f379 Fixed JayRock status response, 2020-11-13 01:23:06 +01:00
DebaucheryLibrarian
cc7c9f3b31 1.140.2 2020-11-13 01:16:27 +01:00
DebaucheryLibrarian
8a22ff07a6 Merged legacy JayRock scraper into new scraper for CosPimps. 2020-11-13 01:16:17 +01:00
DebaucheryLibrarian
2063d66550 Ignoring placeholder avatar in Jay Rock scraper. 2020-11-10 22:44:15 +01:00
DebaucheryLibrarian
e698146d94 1.140.1 2020-11-05 02:22:23 +01:00
DebaucheryLibrarian
4408507371 Added creampie tag photo. 2020-11-05 02:22:15 +01:00
DebaucheryLibrarian
574f53c66d 1.140.0 2020-11-04 04:03:02 +01:00
DebaucheryLibrarian
a3c18ca577 Rewrote Jay Rock scraper for new website. 2020-11-04 04:02:51 +01:00
DebaucheryLibrarian
ec7acd46a0 1.139.7 2020-11-03 03:36:43 +01:00
DebaucheryLibrarian
6976eb337d Added various tag photos. 2020-11-03 03:36:34 +01:00
DebaucheryLibrarian
994413c509 1.139.6 2020-11-02 04:55:16 +01:00
DebaucheryLibrarian
0078dba085 Added various tag photos. 2020-11-02 04:49:16 +01:00
DebaucheryLibrarian
9a636b81a6 1.139.5 2020-11-01 05:25:38 +01:00
DebaucheryLibrarian
734de26559 Added various tag photos. 2020-11-01 05:25:29 +01:00
DebaucheryLibrarian
aa5e9a9c8b 1.139.4 2020-11-01 03:25:37 +01:00
DebaucheryLibrarian
8586817963 Fixed Insex html table selector. 2020-11-01 03:25:30 +01:00
DebaucheryLibrarian
ccca6a7714 1.139.3 2020-11-01 02:58:29 +01:00
DebaucheryLibrarian
c1fffe5cdb Added alternative layout to Insex for updated Topgrl and Sexually Broken sites. 2020-11-01 02:58:21 +01:00
DebaucheryLibrarian
7c11b2204e 1.139.2 2020-10-31 04:53:50 +01:00
DebaucheryLibrarian
72859ede85 Added various tag photos. 2020-10-31 04:53:40 +01:00
DebaucheryLibrarian
30c002d0f2 1.139.1 2020-10-30 17:51:56 +01:00
DebaucheryLibrarian
3855d96135 Removed 'null' from scene tile URL if no slug is available. 2020-10-30 17:51:47 +01:00
DebaucheryLibrarian
7974be05e9 1.139.0 2020-10-30 17:37:19 +01:00
DebaucheryLibrarian
39f8c037a5 Replaced bhttp with patched fork. Improved Jesse Loads Monster Facials scraper reliability (WIP). Added various tag photos. 2020-10-30 17:37:10 +01:00
DebaucheryLibrarian
4af7597441 1.138.9 2020-10-29 16:06:27 +01:00
DebaucheryLibrarian
c37d4ad01f Filtering invalid actors from releases before storing. 2020-10-29 16:06:20 +01:00
DebaucheryLibrarian
2801732f57 1.138.8 2020-10-29 15:21:08 +01:00
DebaucheryLibrarian
b188bc5744 Filtering out empty or unidentified scenes from update scraper, with warning. Improved Jesse Loads Monster Facials reliability. 2020-10-29 15:20:59 +01:00
DebaucheryLibrarian
f4b1fb4831 1.138.7 2020-10-28 15:28:43 +01:00
DebaucheryLibrarian
8c553d5b3d Added traxxx dummy network to default excludes. 2020-10-28 15:28:39 +01:00
DebaucheryLibrarian
e40d7ba181 1.138.6 2020-10-28 03:51:15 +01:00
DebaucheryLibrarian
4469376dd2 Using temporary table instead of WHERE IN to stack depth error when finding duplicate actors. 2020-10-28 03:50:52 +01:00
DebaucheryLibrarian
64a52fbb1e 1.138.5 2020-10-28 01:36:21 +01:00
DebaucheryLibrarian
bf9b334b73 Adding scraper config by scraper slug to current 'includes' parameter. 2020-10-28 01:36:13 +01:00
DebaucheryLibrarian
1869877178 Added release covers table to flush routine. 2020-10-27 02:40:30 +01:00
DebaucheryLibrarian
209fe67bb0 1.138.4 2020-10-27 02:34:15 +01:00
DebaucheryLibrarian
7257776ba8 Mapping avatar to media ID in orphan delete. 2020-10-27 02:34:06 +01:00
DebaucheryLibrarian
af7fa56e02 1.138.3 2020-10-27 02:10:03 +01:00
DebaucheryLibrarian
1d3ec96e8d Detecting profile avatars in orphaned media flush. 2020-10-27 02:09:52 +01:00
DebaucheryLibrarian
99a7bfeb4c Added traxxx dummy network. 2020-10-26 01:42:38 +01:00
DebaucheryLibrarian
15810333b9 1.138.2 2020-10-25 21:43:45 +01:00
DebaucheryLibrarian
0027be65eb Fixed CzechAV to accomodate teasers instead of photos. Fixed error trying to flush non-existent media file. 2020-10-25 21:43:36 +01:00
DebaucheryLibrarian
0e804db130 1.138.1 2020-10-25 01:29:41 +02:00
DebaucheryLibrarian
57a44d6643 Expanded README. 2020-10-25 01:29:25 +02:00
DebaucheryLibrarian
7e3e0d8f30 Merge branch 'experimental' into master 2020-10-25 01:22:50 +02:00
DebaucheryLibrarian
07b94f1513 Updated README. 2020-10-25 01:22:40 +02:00
DebaucheryLibrarian
9619f7e7ed 1.138.0 2020-10-25 01:17:51 +02:00
DebaucheryLibrarian
0bd7fca876 Added orphaned media flush and batch release flush. 2020-10-25 00:52:40 +02:00
DebaucheryLibrarian
ef852f0191 1.137.6 2020-10-20 21:04:36 +02:00
DebaucheryLibrarian
6791053c83 Fixed entity alias available through wrong type endpoint. 2020-10-20 21:04:29 +02:00
DebaucheryLibrarian
47238b2969 1.137.5 2020-10-20 15:37:50 +02:00
DebaucheryLibrarian
9f3c686913 Added thumbnail and favicon to entity REST API. 2020-10-20 15:37:42 +02:00
DebaucheryLibrarian
cefd91a7b9 1.137.4 2020-10-20 15:29:51 +02:00
DebaucheryLibrarian
ecdd6d8fb0 Added logo path to entity API. 2020-10-20 15:28:58 +02:00
DebaucheryLibrarian
60eb599416 Added alias to entity search query. 2020-10-20 00:25:32 +02:00
DebaucheryLibrarian
8e7b944b52 1.137.3 2020-10-20 00:21:25 +02:00
DebaucheryLibrarian
6b17f9d1f2 Allowing entity to be fetched by alias. 2020-10-20 00:21:15 +02:00
DebaucheryLibrarian
cb459d4cc7 1.137.2 2020-10-20 00:08:24 +02:00
DebaucheryLibrarian
d795266114 Removed no-date-limit as argument due yargs conflict. 2020-10-20 00:08:14 +02:00
DebaucheryLibrarian
3e303e4b10 1.137.1 2020-10-20 00:05:34 +02:00
DebaucheryLibrarian
2f8fca0327 Added missing-date as config and argument alias. 2020-10-20 00:05:23 +02:00
DebaucheryLibrarian
4a900cbbeb Renamed nullDateLimit to noDateLimit in config. 2020-10-20 00:03:22 +02:00
DebaucheryLibrarian
5bdbb5ec62 Renamed null-date-limit to no-date-limit, added old as alias. 2020-10-20 00:01:29 +02:00
DebaucheryLibrarian
ce78e07444 1.137.0 2020-10-19 02:02:49 +02:00
DebaucheryLibrarian
593ce27312 Added rudimentary scene and entity scene remove. 2020-10-19 02:02:21 +02:00
DebaucheryLibrarian
2536405dba Fixed entity API database query. 2020-10-18 00:01:34 +02:00
DebaucheryLibrarian
ca22aedaaa Added rudimentary API documentation to README. 2020-10-17 22:54:00 +02:00
DebaucheryLibrarian
e6c52002f0 Added tags and entities to REST API.. 2020-10-16 23:00:03 +02:00
DebaucheryLibrarian
3d86e52b25 1.136.0 2020-10-15 01:45:36 +02:00
DebaucheryLibrarian
f38233053e Removed debug log. 2020-10-15 01:45:13 +02:00
DebaucheryLibrarian
99a4751c20 Returning results from new pagination. 2020-10-14 03:17:03 +02:00
DebaucheryLibrarian
013e85cf2a Added various tag photos. 2020-10-13 04:04:09 +02:00
DebaucheryLibrarian
7c856c267d Added revised next page determination. 2020-10-12 04:08:22 +02:00
DebaucheryLibrarian
8aefb8eddb Added and updated tag photos. 2020-10-07 03:40:19 +02:00
DebaucheryLibrarian
3f843cc0fc Added version to stats page. 2020-09-25 21:21:26 +02:00
DebaucheryLibrarian
566c20ea7e Added various tag photos. Renamed some toy tags. 2020-09-21 05:11:24 +02:00
DebaucheryLibrarian
a9c1a91571 1.135.9 2020-09-19 00:12:21 +02:00
DebaucheryLibrarian
e78bfe4c22 Upgraded knex and pg versions. 2020-09-19 00:12:15 +02:00
DebaucheryLibrarian
aa265fc350 1.135.8 2020-09-18 23:25:22 +02:00
DebaucheryLibrarian
c94e1aaea9 Handling missing trailers in Kink scraper. 2020-09-18 23:25:15 +02:00
DebaucheryLibrarian
d194d7107d 1.135.7 2020-09-18 22:43:54 +02:00
DebaucheryLibrarian
3789ef51f2 (Temporarily) removed studio filter from entity query for performance reasons. 2020-09-18 22:43:45 +02:00
DebaucheryLibrarian
88c16e096a 1.135.6 2020-09-18 03:27:07 +02:00
DebaucheryLibrarian
3c9468b0f1 Fixed wrong MindGeek session acquire URL. 2020-09-18 03:27:00 +02:00
DebaucheryLibrarian
38b90b3d4c 1.135.5 2020-09-18 02:54:15 +02:00
DebaucheryLibrarian
a4929819df Using channel URL instead of composed URL for session retrieval, should fix Brazzers. 2020-09-18 02:54:05 +02:00
DebaucheryLibrarian
53e8495d06 1.135.4 2020-09-17 14:49:56 +02:00
DebaucheryLibrarian
a9fa71e455 Fixed predata parameter in Assylum scraper. 2020-09-17 14:49:45 +02:00
DebaucheryLibrarian
9c0efd7bf9 1.135.3 2020-09-17 04:03:37 +02:00
DebaucheryLibrarian
796a624d2b Changed Updated various tag posters. 2020-09-17 04:01:40 +02:00
DebaucheryLibrarian
ab9d6666cf 1.135.2 2020-09-17 02:31:08 +02:00
DebaucheryLibrarian
1a8de4fcf6 Added mimetype verification option to media source to ensure server returned a plausible file. Added additional fallbacks to Jules Jordan poster scraper for Amateur Allure. 2020-09-17 02:30:58 +02:00
DebaucheryLibrarian
6d1f83bc40 1.135.1 2020-09-17 00:35:50 +02:00
DebaucheryLibrarian
0190ee9531 Fixed Porn Doe's poster query. Checking style attribute existence in qu before attempting to use it. 2020-09-17 00:35:41 +02:00
DebaucheryLibrarian
718abdfdba 1.135.0 2020-09-16 04:55:48 +02:00
DebaucheryLibrarian
6fb15fb591 Added Dorcel Club with scene and actor scraping. Added count method to qu. 2020-09-16 04:55:30 +02:00
DebaucheryLibrarian
34e087098b 1.134.1 2020-09-16 02:39:09 +02:00
DebaucheryLibrarian
a8c525f4fc Hard-coded Pascal White as the male actor for Pascal's Sub Sluts. Added female gender to all Sub Sluts. 2020-09-16 02:38:59 +02:00
DebaucheryLibrarian
5ef160c98d 1.134.0 2020-09-16 01:47:14 +02:00
DebaucheryLibrarian
7c4dd03a8c Ignoring 'Lockdown Submissions' as actor for Pascals Subsluts. 2020-09-16 01:45:58 +02:00
DebaucheryLibrarian
7fd7005776 Added profile scraper to Pascals Subsluts. 2020-09-16 01:42:15 +02:00
DebaucheryLibrarian
286d48c02b Added Bang Bros Vault logo. 2020-09-14 16:43:46 +02:00
DebaucheryLibrarian
08edf70194 1.133.0 2020-09-14 16:01:31 +02:00
DebaucheryLibrarian
bc34c6edb4 Added Bang Bros Vault channel. 2020-09-14 16:01:27 +02:00
DebaucheryLibrarian
def6e8792c 1.132.0 2020-09-14 02:40:54 +02:00
DebaucheryLibrarian
ba7419d3b0 Added basic Pascals Subsluts scraper. 2020-09-14 02:40:27 +02:00
DebaucheryLibrarian
65d079eec0 1.131.0 2020-09-14 00:53:59 +02:00
DebaucheryLibrarian
beeaebbfb7 Added CzechAV. 2020-09-14 00:53:41 +02:00
DebaucheryLibrarian
115e88cd93 1.130.2 2020-09-13 01:42:01 +02:00
DebaucheryLibrarian
b57b0a38f5 Fixed infinite pagination when scraping upcoming scenes. 2020-09-13 01:41:52 +02:00
DebaucheryLibrarian
52a22b6eca 1.130.1 2020-09-12 03:33:35 +02:00
DebaucheryLibrarian
2612c55c85 Fixed 'clear all' button not showing for networks in channel filter. 2020-09-12 03:33:23 +02:00
DebaucheryLibrarian
63e4c7d888 Merge branch 'master' into experimental 2020-09-12 03:10:16 +02:00
DebaucheryLibrarian
b791458cb8 1.130.0 2020-09-12 03:10:10 +02:00
DebaucheryLibrarian
62f5d5111a Added basic co-star actor filter to actor page. 2020-09-12 03:09:05 +02:00
DebaucheryLibrarian
24fb267b40 1.129.0 2020-09-11 22:45:55 +02:00
DebaucheryLibrarian
4f29dd4f8c Improved sidebar search styling and behavior. 2020-09-11 22:45:44 +02:00
DebaucheryLibrarian
08db1d63bf Showing overflowing menu items in header on mobile. Added search to sidebar (WIP). Added breakpoint. 2020-09-11 03:13:51 +02:00
DebaucheryLibrarian
0e3145a051 1.128.7 2020-09-11 02:36:49 +02:00
DebaucheryLibrarian
eb6337f6fb Fixed upcoming Jules Jordan scene without teaser breaking scraper. 2020-09-11 02:36:36 +02:00
DebaucheryLibrarian
9499cd0265 1.128.6 2020-09-11 02:29:25 +02:00
DebaucheryLibrarian
471f8f2bec Improve date range precision. 2020-09-11 02:29:14 +02:00
DebaucheryLibrarian
aa74c1c721 1.128.5 2020-09-10 23:49:43 +02:00
DebaucheryLibrarian
0e8024adf1 Changed next page determination to ensure --after is followed even if there are no unique releases. 2020-09-10 23:49:24 +02:00
DebaucheryLibrarian
a833476437 1.128.4 2020-09-10 17:54:33 +02:00
DebaucheryLibrarian
bec097f14d Returning status codes from Gamma fetch. 2020-09-10 17:54:23 +02:00
DebaucheryLibrarian
196449fbd6 1.128.3 2020-09-10 17:41:39 +02:00
DebaucheryLibrarian
7f8704ee2c Using http instead of bhttp for Gamma. 2020-09-10 17:41:29 +02:00
DebaucheryLibrarian
d8866172c1 1.128.2 2020-09-10 03:56:19 +02:00
DebaucheryLibrarian
5ee5b270ef Added avatars to Gamma latest API. 2020-09-10 03:56:09 +02:00
DebaucheryLibrarian
89cb4f4770 1.128.1 2020-09-10 03:43:26 +02:00
DebaucheryLibrarian
88eeab410b Added filter parameter to Gamma scraper to distinguish Evil Angel exclusives from channel scenes. 2020-09-10 03:43:16 +02:00
DebaucheryLibrarian
cecc01d216 1.128.0 2020-09-10 03:17:33 +02:00
DebaucheryLibrarian
0d4893b13c Added Evil Angel channels with logos. 2020-09-10 03:17:19 +02:00
DebaucheryLibrarian
d081b88af2 1.127.0 2020-09-09 22:23:52 +02:00
DebaucheryLibrarian
a285313bae Changed Evil Angel type to network and added Anal Acrobats. 2020-09-09 22:23:37 +02:00
DebaucheryLibrarian
860d88fe56 Fixed tag tile links. 2020-09-09 21:36:52 +02:00
DebaucheryLibrarian
968aabf893 1.126.6 2020-09-09 04:22:58 +02:00
DebaucheryLibrarian
d46ac6206d Added dedicated scene function and pagination to tag page. 2020-09-09 04:22:43 +02:00
DebaucheryLibrarian
6bb8d26561 1.126.5 2020-09-09 03:45:46 +02:00
DebaucheryLibrarian
2b7ace0356 Merge branch 'experimental' into master 2020-09-09 03:45:17 +02:00
DebaucheryLibrarian
ac66606135 Including networks in filter count. Preserving query between date ranges. Allowing --latest to be used without --last. 2020-09-09 03:45:00 +02:00
DebaucheryLibrarian
528986cd4f 1.126.4 2020-09-09 03:28:53 +02:00
DebaucheryLibrarian
dcaaa4e689 Merge branch 'experimental' into master 2020-09-09 03:28:40 +02:00
DebaucheryLibrarian
d1cdd60ee8 Enabled network filters for actors. Separated filter definition for entities. 2020-09-09 03:28:33 +02:00
DebaucheryLibrarian
2d15da9a39 1.126.3 2020-09-08 16:52:44 +02:00
DebaucheryLibrarian
611eceff2b Merge branch 'experimental' into master 2020-09-08 16:52:40 +02:00
DebaucheryLibrarian
00f1fc39fa Changed webpack config to use require. 2020-09-08 16:52:31 +02:00
DebaucheryLibrarian
8af9879b08 1.126.2 2020-09-08 15:45:07 +02:00
DebaucheryLibrarian
501e980e73 Merge branch 'experimental' into master 2020-09-08 15:45:02 +02:00
DebaucheryLibrarian
8aaa88770f Accounting for Windows paths in logger. 2020-09-08 15:44:55 +02:00
DebaucheryLibrarian
ac2b7e769d Fixed checkmarks on channel filters. 2020-09-08 03:54:21 +02:00
DebaucheryLibrarian
03ba35d65a Fixed include object. Fixed qu's undefined URL handling. 2020-09-08 03:26:34 +02:00
DebaucheryLibrarian
f0c4f33eea 1.126.1 2020-09-08 02:20:32 +02:00
DebaucheryLibrarian
7c6243cf33 Combined scene and movie components. 2020-09-08 02:20:15 +02:00
DebaucheryLibrarian
5bf5be94bb Not showing networks in overview when all children are either networks or independent channels. 2020-09-05 04:08:10 +02:00
DebaucheryLibrarian
c96e10b33d Improved SFW and tag media seed file to allow updates. 2020-09-05 02:57:24 +02:00
DebaucheryLibrarian
bba73c4f31 1.126.0 2020-09-05 01:57:45 +02:00
DebaucheryLibrarian
e90bb63a8f Added American Pornstar. Improved Jules Jordan scraper to accomodate for American Pornstar. Changed entity logo mogrify settings to ensure both minimum height and width. 2020-09-05 01:56:54 +02:00
DebaucheryLibrarian
3ddba0816e 1.125.0 2020-09-04 03:18:27 +02:00
DebaucheryLibrarian
5386f81cda Merge branch 'experimental' into master 2020-09-04 03:18:21 +02:00
DebaucheryLibrarian
3c84a814a8 Added Zero Tolerance with Addicted 2 Girls and GenderX. 2020-09-04 03:07:28 +02:00
DebaucheryLibrarian
bd04cfd898 1.124.1 2020-09-03 23:32:30 +02:00
DebaucheryLibrarian
c7c0d80cf0 Merge branch 'experimental' into master 2020-09-03 23:31:34 +02:00
DebaucheryLibrarian
21cc88dfea Moved tag photos from behind-the-scenes/ to bts/. 2020-09-03 23:31:29 +02:00
DebaucheryLibrarian
0f40141be7 Fixed double photos on scene page. 2020-09-03 23:30:42 +02:00
DebaucheryLibrarian
40b065abd9 1.124.0 2020-09-03 22:24:52 +02:00
DebaucheryLibrarian
d3b0c1d82c Merge branch 'experimental' into master 2020-09-03 22:24:40 +02:00
DebaucheryLibrarian
c187a27123 Added Hookup Hotshot. 2020-09-03 22:22:12 +02:00
DebaucheryLibrarian
f6353ca14c Storing actor profile URL when provided from scene page. 2020-08-31 02:43:41 +02:00
DebaucheryLibrarian
1bfdf4b232 Storing actor profiles from scene pages. 2020-08-30 04:18:47 +02:00
DebaucheryLibrarian
95f57c9f5e 1.123.8 2020-08-26 23:59:45 +02:00
DebaucheryLibrarian
fdfb0c7928 Merge branch 'experimental' into master 2020-08-26 23:59:41 +02:00
DebaucheryLibrarian
53b0101a12 Added new Naughty America sites. 2020-08-26 23:59:29 +02:00
DebaucheryLibrarian
8a21ce98b1 1.123.7 2020-08-26 02:01:58 +02:00
DebaucheryLibrarian
3ede565971 Merge branch 'experimental' into master 2020-08-26 02:01:45 +02:00
DebaucheryLibrarian
8611d738b0 Using UTC to query date ranges. Removed stray console log from MindGeek scraper. 2020-08-26 02:01:38 +02:00
DebaucheryLibrarian
bc51a91734 1.123.6 2020-08-24 18:24:20 +02:00
DebaucheryLibrarian
06988073d8 Merge branch 'experimental' into master 2020-08-24 18:24:16 +02:00
DebaucheryLibrarian
52f66e7982 Fixed undefined location in FreeOnes scraper. 2020-08-24 18:24:07 +02:00
DebaucheryLibrarian
621e40304f 1.123.5 2020-08-24 05:13:47 +02:00
DebaucheryLibrarian
7fed5b7138 Moved Brazzers to MindGeek scraper to support new site. 2020-08-24 05:13:34 +02:00
DebaucheryLibrarian
801774ab28 1.123.4 2020-08-23 03:41:33 +02:00
DebaucheryLibrarian
d8b8dfa299 Not creating batch ID when no movies are to be stored. 2020-08-23 03:41:29 +02:00
DebaucheryLibrarian
fe5daefd61 1.123.3 2020-08-23 03:32:10 +02:00
DebaucheryLibrarian
42247449f8 Merge branch 'experimental' into master 2020-08-23 03:31:52 +02:00
DebaucheryLibrarian
c3d771c8fc Hush scraper uses children from entity argument for filter regexp, instead of making its own database request. 2020-08-23 03:31:37 +02:00
DebaucheryLibrarian
278b74e78c Providing duplicate releases in predata. Using duplicates for filtering scenes without channel in Hush scraper. 2020-08-23 02:43:10 +02:00
DebaucheryLibrarian
3a5ea3dd9a Returning duplicate releases from pagination. 2020-08-22 04:22:56 +02:00
DebaucheryLibrarian
b5b0792c90 1.123.2 2020-08-22 01:57:46 +02:00
DebaucheryLibrarian
9361f6bc53 Merge branch 'experimental' into master 2020-08-22 01:57:32 +02:00
DebaucheryLibrarian
5f5c48ea05 Fixed pagination behavior for upcoming scenes. 2020-08-22 01:57:23 +02:00
DebaucheryLibrarian
7c052fedfc Fixed map error. 2020-08-22 00:28:22 +02:00
DebaucheryLibrarian
ec3ea892af 1.123.1 2020-08-21 03:56:06 +02:00
DebaucheryLibrarian
c7dc7de0c3 Merge branch 'experimental' into master 2020-08-21 03:56:01 +02:00
DebaucheryLibrarian
1c17cd1be5 Improved update scrape pagination and limits. 2020-08-21 03:55:51 +02:00
DebaucheryLibrarian
4ec89e2cc8 Added upcoming, profile and detailed scene actor scraping to InTheCrack. Fixed clip upsert. 2020-08-20 23:35:18 +02:00
DebaucheryLibrarian
552e6da392 Improved clip layout. Using format module for duration and time. 2020-08-20 20:48:52 +02:00
DebaucheryLibrarian
501e764c21 Renamed chapters to clips. Fixed Vixen trailers. 2020-08-20 19:52:02 +02:00
DebaucheryLibrarian
2b101c2967 1.123.0 2020-08-20 04:57:55 +02:00
DebaucheryLibrarian
23cf5febec Merge branch 'experimental' into master 2020-08-20 04:57:49 +02:00
DebaucheryLibrarian
2835c66694 Added chapters and shoot location. Added In The Crack. 2020-08-20 04:57:38 +02:00
DebaucheryLibrarian
5fb84d153e 1.122.1 2020-08-19 21:49:11 +02:00
DebaucheryLibrarian
5767bfb5a2 Merge branch 'experimental' into master 2020-08-19 21:49:04 +02:00
DebaucheryLibrarian
fd4477bc50 Improved 'new' sorting. 2020-08-19 21:48:55 +02:00
DebaucheryLibrarian
e896d52968 Added separate task queue for video streams to prevent ffmpeg overstressing the CPU. Fixed entity parent in scene REST API. 2020-08-17 15:53:20 +02:00
DebaucheryLibrarian
6bbe1b41c2 1.122.0 2020-08-15 19:04:53 +02:00
DebaucheryLibrarian
bd6396d7a8 Merge branch 'experimental' 2020-08-15 19:04:47 +02:00
DebaucheryLibrarian
b3435c97c3 Added footer and basic stats page. 2020-08-15 19:04:33 +02:00
DebaucheryLibrarian
d7974f057f Fixed scene tile detail word wrap. Fixed Score posters. 2020-08-15 02:05:35 +02:00
DebaucheryLibrarian
50c5f921f5 Using new bulk insert utility for releases, media and actors. 2020-08-14 23:21:53 +02:00
DebaucheryLibrarian
e996a45bf5 Added new bulk upsert utility. 2020-08-14 23:05:25 +02:00
DebaucheryLibrarian
b3f784686f Improved entity provision behavior. 2020-08-14 00:32:59 +02:00
DebaucheryLibrarian
77566eae0d Fixed and documented entity configuration and query. 2020-08-13 23:59:54 +02:00
DebaucheryLibrarian
59e2124407 Removed type property from scenes API. 2020-08-13 16:10:58 +02:00
DebaucheryLibrarian
f8c9b69f4b Allowing --actors-update to be used without --actors. 2020-08-12 21:04:38 +02:00
DebaucheryLibrarian
d14034d38b Defauling --actors-update to 1900-01-01 2020-08-12 21:00:50 +02:00
DebaucheryLibrarian
1d3b9a19bc Updated profile scraping documentation. 2020-08-12 20:53:09 +02:00
DebaucheryLibrarian
7413d7db25 Improved and documented actor profile scraping. 2020-08-12 20:51:08 +02:00
DebaucheryLibrarian
5cabeed19d Modularized release component between movie and scene. Added Kink Classics channel. 2020-08-12 03:30:20 +02:00
DebaucheryLibrarian
40aed1086f Showing actors, tags and date on movie tiles. 2020-08-10 21:39:55 +02:00
DebaucheryLibrarian
dd1ea597d4 Added slide effect to sidebar. 2020-08-08 22:18:55 +02:00
DebaucheryLibrarian
a7d5bef93f Filtering undefined scenes property from movies. Added movie page scraper to Elegant Angel. 2020-08-08 18:10:59 +02:00
DebaucheryLibrarian
7bfa5a6cc4 Defaulting actors view to 'all'. 2020-08-02 03:51:52 +02:00
DebaucheryLibrarian
b4f0501765 Scraping from Cherry Pimps when available. Showing cover in movie tile. 2020-08-02 03:44:14 +02:00
DebaucheryLibrarian
767437d9aa Added movie tile. Fixed actor header. Larger breakpoint for nav menu. 2020-08-01 15:11:07 +02:00
DebaucheryLibrarian
6c5a62353c Moved movies to separate table. 2020-07-25 03:44:19 +02:00
DebaucheryLibrarian
bfd54e94e7 Improved Team Skeet profile scraper. 2020-07-23 21:00:16 +02:00
DebaucheryLibrarian
126fd5c0ff Added various Team Skeet logos. 2020-07-23 18:27:49 +02:00
DebaucheryLibrarian
747c2e1637 Checking nationality against alpha2 and alpha2. Improved Team Skeet profile scraper. 2020-07-23 04:39:12 +02:00
DebaucheryLibrarian
23e4f87af0 Added profile scraping and Hoby Buchanon to Team Skeet. 2020-07-23 04:29:46 +02:00
DebaucheryLibrarian
9ef5ea8fb6 Added the awkward pagination to PornCZ scraper. 2020-07-23 00:55:55 +02:00
DebaucheryLibrarian
46c6c4dd21 Added PornCZ. 2020-07-22 04:12:20 +02:00
DebaucheryLibrarian
9d89a38490 Added actor page scene thumbnails to Hitzefrei scraper. 2020-07-21 04:10:16 +02:00
DebaucheryLibrarian
d56da74168 Added Hitzefrei. Fixed date averaging. 2020-07-21 04:04:07 +02:00
DebaucheryLibrarian
dff4d15872 Updated profile scrapers to use base actor instead of actor name. Fixes for Reality Kings and Cherry Pimps scrapers. 2020-07-21 01:44:51 +02:00
DebaucheryLibrarian
939eba8e61 Changed qu's HTML element detection. Passing base actor instead of actorName to profile scrapers. 2020-07-21 01:16:26 +02:00
DebaucheryLibrarian
0e4c0d8fff Added channel filter. 2020-07-20 04:20:33 +02:00
DebaucheryLibrarian
5291a87587 Added 'clear all' button to tag filter. Tag name never removes tag. Added actor scene scraping to LegalPorno. 2020-07-19 19:40:21 +02:00
DebaucheryLibrarian
5da29227e8 1.121.1 2020-07-19 04:25:31 +02:00
DebaucheryLibrarian
70bf00e844 Moved tag filter modes to postgres function. 2020-07-19 04:25:07 +02:00
DebaucheryLibrarian
cf999896d5 1.121.0 2020-07-19 03:52:43 +02:00
DebaucheryLibrarian
2bb511cd99 Added 'match all' tag filter for actors and toggle to 'match any'. 2020-07-19 03:52:36 +02:00
DebaucheryLibrarian
f147d0f3b3 1.120.1 2020-07-18 05:13:26 +02:00
DebaucheryLibrarian
1220bad26d Merge branch 'experimental' 2020-07-18 05:13:07 +02:00
DebaucheryLibrarian
689c701f34 Various tag photos. 2020-07-18 05:12:32 +02:00
DebaucheryLibrarian
a3d281192d Reordered scraper arguments. Fixed Jules Jordan scraper for Amateur Allure. 2020-07-17 23:27:59 +02:00
DebaucheryLibrarian
f59e809713 Added experimental movie page scraping with Elegant Angel. 2020-07-17 04:33:05 +02:00
DebaucheryLibrarian
48f7a25a22 1.120.0 2020-07-17 03:41:27 +02:00
DebaucheryLibrarian
0b57ebb10f Merge branch 'experimental' 2020-07-17 03:41:20 +02:00
DebaucheryLibrarian
a88c2f0760 Added m3u8 stream support to media module. Added Elegant Angel. Added regex parameter to qu's number method. Various tags. 2020-07-17 03:39:13 +02:00
DebaucheryLibrarian
5e850f12c6 1.119.2 2020-07-16 15:55:15 +02:00
DebaucheryLibrarian
66d6322c1d Updated GraphQL queries to Datetime. Updated template to latest guideline. 2020-07-16 15:55:03 +02:00
DebaucheryLibrarian
d0e61978d6 Added Elegant Angel to database (w/o scraper). 2020-07-16 03:48:29 +02:00
DebaucheryLibrarian
faee5bb613 1.119.1 2020-07-16 03:47:13 +02:00
DebaucheryLibrarian
6adfded074 Refactored Mike Adriano scraper. Changed logo and favicon. Added style methods to qu. 2020-07-16 03:47:07 +02:00
DebaucheryLibrarian
6584a46d53 1.119.0 2020-07-15 05:12:43 +02:00
DebaucheryLibrarian
5b886b3917 Improved actor extraction for fcuk scraper. Changed 'copyright' to 'credit'. Redused entity page favicon size. 2020-07-15 05:12:29 +02:00
DebaucheryLibrarian
c62df2228b Added scraper for FCUK's coed sites. 2020-07-15 04:51:39 +02:00
DebaucheryLibrarian
17b3ba1272 Added partial 'fcuk' (Exploited College Girls) scraper. Added file parameter for actor names and scene URLs. 2020-07-15 03:24:47 +02:00
DebaucheryLibrarian
eca54c2a09 Improved sidebar design, added sfw and theme toggles. 2020-07-15 00:15:00 +02:00
DebaucheryLibrarian
cb51a2a81b 1.118.1 2020-07-14 21:21:34 +02:00
DebaucheryLibrarian
90fc7a0d9d Fixed Kink profile scraper returning partial matches. Removed parent from BAM Visions and Vogov. 2020-07-14 21:21:27 +02:00
DebaucheryLibrarian
9a6ab35c21 1.118.0 2020-07-14 04:36:41 +02:00
DebaucheryLibrarian
74b15aa8e9 Added 5K Porn and 5K Teens. 2020-07-14 04:36:14 +02:00
DebaucheryLibrarian
032d8bee1b 1.117.0 2020-07-14 03:46:50 +02:00
DebaucheryLibrarian
b7be97fcf9 Added Amateur Euro, For Bondage, Mamacitaz, TransBella and VIP Sex Vault. Refactored Kelly Madison scraper using qu, fixed trailers and improved reliability. 2020-07-14 03:46:31 +02:00
598e93728f 1.116.1 2020-07-13 04:33:03 +02:00
c6ca219505 Merge branch 'experimental' 2020-07-13 04:32:25 +02:00
70aeb4b989 Updated various tag posters. 2020-07-13 04:32:13 +02:00
6cb96766db 1.116.0 2020-07-13 03:51:32 +02:00
1b04348a8b Merge branch 'experimental' 2020-07-13 03:51:28 +02:00
1eab3be7f6 Added Lets Doe It scraper. Added timestamp matching to qu's duration method. 2020-07-13 03:51:17 +02:00
3575d57608 1.115.0 2020-07-13 00:12:15 +02:00
50e5525591 Merge branch 'experimental' 2020-07-13 00:12:11 +02:00
6fd2bc2687 Added Kink profile scraper. Fixed --force causing media collisions. 2020-07-13 00:12:01 +02:00
a7707b7b28 Merged DDF Network with Porn World. Fixed and updated DDF/PW scraper. 2020-07-12 22:36:53 +02:00
70c60e93ac Scraping Perv City updates to network entity. 2020-07-12 05:25:27 +02:00
51317f3e51 1.114.2 2020-07-12 05:10:39 +02:00
98a9fdc6a7 Merge branch 'experimental' 2020-07-12 05:10:32 +02:00
9c8cfe3bdb Re-wrote broken Perv City scraper, added profile scraping. 2020-07-12 05:10:23 +02:00
8d3bdd8a2b 1.114.1 2020-07-12 03:04:41 +02:00
051556936e Added last Killergram logos. 2020-07-12 01:59:16 +02:00
37d6b747ac Added Killergram logos to repo. Added Hardcore Chain Smokers logo. 2020-07-11 04:47:49 +02:00
1c5958d1d2 Added various Killergram logos. 2020-07-11 04:37:40 +02:00
bd4f48fe04 1.114.0 2020-07-10 03:42:46 +02:00
e8c55512e2 Added image processing options to media module. Cropping Killergram avatars. Overwriting images when --force is used. 2020-07-10 03:42:08 +02:00
4c551cc15f Fixed pagination for Killergram, added pagination to actor profiles, added Killergram Platinum. Added experimental m3u8 stream support. 2020-07-10 02:01:23 +02:00
067c4a62ff 1.113.2 2020-07-09 04:31:30 +02:00
a013d73400 Added Killergram. 2020-07-09 04:31:27 +02:00
3857eae158 1.113.1 2020-07-09 02:01:34 +02:00
f07789a3e1 Merge branch 'refactor' 2020-07-09 02:01:27 +02:00
44a8ced30c Separated actor expand buttons. Refactored Brazzers scraper. Fixed actor releases not included in shallow scrape. Added number query and data-src default to qu img. Updated README. Removed post-install migrate and seed. 2020-07-09 02:00:54 +02:00
bae4070621 1.113.0 2020-07-08 04:35:33 +00:00
906ce8759b Merge branch 'refactor' 2020-07-08 04:32:13 +00:00
17d46e804e Updated tag photos. 2020-07-08 04:58:12 +02:00
9e5d3aa139 Added 'visible' property to entities. Added various Teen Core Club logos. 2020-07-08 02:53:46 +02:00
50154baa40 Changed entity logo thumbnails from x80 (height) to 300x (width) for improved sharpness in square logos. 2020-07-07 19:33:07 +02:00
0fc85e5de2 Updated actor photos to use expand and load events with scroll component. Fixed actor description independent entity logo. Fixed unwanted actor horizontal scroll. 2020-07-07 19:23:36 +02:00
0fd1edd8fd Added Teen Core Club profile scraper. Added all TCC channels. Added various TCC logos. 2020-07-07 04:37:12 +02:00
abe7de5701 Added scene scraping to Teen Core Club. 2020-07-06 04:13:48 +02:00
9b1d38d9ff Updating scroll component on image load. 2020-07-06 02:40:10 +02:00
af9c4a36c6 Tracking new breakpoint file. 2020-07-05 16:34:39 +02:00
f8a32e841c Defined new breakpoint names. 2020-07-05 04:40:57 +02:00
e4144409f0 Removed all old SASS color variables. 2020-07-05 04:27:46 +02:00
f4ef2d4cc2 Auto-shrinking pagination. Various Teen Core Club curations. 2020-07-05 04:10:35 +02:00
c47fae3a1b Setting composed title as page title. Using Teen Core Club title if it isn't shoot ID. 2020-07-03 04:42:20 +02:00
48a127409e Added date precision. Added Teen Core Club update scraper. 2020-07-03 04:12:56 +02:00
945c2c45ce Updated tag page layout. Added shoot date property. Showing parent favicon on compact entity page. Re-added 'new' indicator on tile. Added Family Sinner to Mile High Media. Various fixes and improvements. 2020-07-03 01:28:22 +02:00
749864e922 Added 'independent' property for network-like channels. Changed release tile design. Adding Brazzers timeline events as tags. Added Property Sex to MindGeek. Changed DP, DAP and DVP tag slugs. Changed Porn Pros logo. Added better BAM Visions and Mug Fucked logos. 2020-07-02 04:04:28 +02:00
6e79381937 Changed release information layout. 2020-07-01 05:04:02 +02:00
1f444e58ce Allowing image sources to specify queue method. Using 5s queue for Whale Member to avoid CDN time-outs. 2020-07-01 04:47:05 +02:00
53870fda89 Improved release detail bar behavior. 2020-07-01 00:25:27 +02:00
240f53047d Minor UI changes. 2020-06-30 04:41:12 +02:00
b803afa973 Added selectable tag function for actors. Implemented experimental filtering by tag. 2020-06-30 04:33:47 +02:00
3fba2d8a77 Improved 'is new' postgres function to deal with skipped batch IDs. 2020-06-30 02:08:48 +02:00
ff384fb734 Fixed search documents to coalesce empty parent entities. 2020-06-30 01:52:17 +02:00
08dc06c810 Improved release media layout. 2020-06-30 01:07:48 +02:00
b22fdd841b Using scroll component for release banner, adding expand button. 2020-06-29 04:43:39 +02:00
8f9eb91b13 Using query instead of parameters for tag filter URI. Added generic scrolling component, using for actor photos and entity children. Removed pagination from filter bar. 2020-06-29 03:55:10 +02:00
98c19b560f Updated mindgeek scraper for entities. Various fixes. 2020-06-28 22:29:18 +02:00
41d7d2fa34 Fixed actor description logos. 2020-06-28 04:22:19 +02:00
f4029f0ef7 Resetting scroll status when navigating between entities. 2020-06-28 04:02:44 +02:00
087d349cec Hiding scroll buttons on small screens. Fixed channel count on overview. 2020-06-28 03:58:16 +02:00
4bf4183a2a Only show entity children expand when overflowing. 2020-06-28 03:28:11 +02:00
6d337e7cb2 Added scroll buttons to entity children. 2020-06-28 03:19:09 +02:00
7d31dd8d52 Fixed seed files for stand-alone channel entities. 2020-06-28 00:44:53 +02:00
3462d7af2a Entity refactor. Facilitating channels without parent. 2020-06-28 00:15:13 +02:00
0e8b4caac3 Added generic entity page. 2020-06-27 04:50:13 +02:00
af56378ee2 Refactored various modules for entities. Updated and refactored Kink scraper. 2020-06-27 02:57:30 +02:00
4959dfd14f Refactored deep and store modules to use entities. 2020-06-25 02:26:25 +02:00
f0a89df6ab Refactoring to use entities over sites and networks. 2020-06-17 04:07:24 +02:00
1907ce1e54 Changed sites from argument query to group by network. 2020-06-15 03:58:35 +02:00
79465d9634 Added Teen Core Club. Changed network to entity in GraphQL query. 2020-06-08 03:41:12 +02:00
09d849eb9d Adding networks and sites as entities, 2020-06-04 01:03:02 +02:00
8abcc7194a Updated Full Porn Network scraper. 2020-05-29 22:43:03 +02:00
9903423caf Added tag filters to all URLs. Improved tag filter styling. 2020-05-27 03:04:18 +02:00
88a88227c4 Applying tag filters to URL. 2020-05-27 01:40:10 +02:00
86377fec5f Enabled pagination on network page. 2020-05-26 04:11:29 +02:00
fe69ec4175 Added new tag filter stub. 2020-05-25 04:39:58 +02:00
b180572d5f Changed sort filters to tabs. 2020-05-25 02:02:28 +02:00
f4c85b7a67 Updated tags and posters. 2020-05-24 05:14:01 +02:00
9843023c1f Added Filthy Family and 'legacy' scraper to Bang Bros. Added trans generic avatar. Added pagination support to site actions. 2020-05-24 03:54:29 +02:00
75d49517b7 Added pagination to actor overview. Lazy loading actor avatars. Reduced hash digest length. 2020-05-23 04:32:50 +02:00
2fcd426b49 Added basic pagination to homepage. 2020-05-22 04:32:16 +02:00
f38be7a706 Added components. 2020-05-22 00:55:11 +02:00
c3de881a2c Always showing actor photos horizontally. 2020-05-21 04:27:45 +02:00
532a4b679b Using batch insert for profiles to prevent errors on large inserts. 2020-05-21 03:44:44 +02:00
703b77897d Added actor scenes to deep fetch sources. 2020-05-20 03:03:42 +02:00
7275fb10e3 Improved Gamma scraper for (XEmpire) actors. 2020-05-20 03:00:46 +02:00
b1b7cd6d50 Fixed Whale Member posters and photos. 2020-05-20 02:23:45 +02:00
b6691e1991 Added release type distinction to REST API. 2020-05-20 01:38:58 +02:00
057362d011 Added basic release and actor API. 2020-05-20 01:11:32 +02:00
6973d39cbd Replacing one or multiple space-like characters with single space in actor description. Using logo thumbnails in descriptions. 2020-05-19 05:06:01 +02:00
52ce2934ac Hiding profile descriptions in compact mode. 2020-05-19 04:51:47 +02:00
c0898b84d6 Showing all unique descriptions on profile with network logo. Fixed Fame Digital scraper. 2020-05-19 04:46:49 +02:00
9883c3d9c2 Not scraping existing actor profiles unless --force is used. 2020-05-19 02:02:48 +02:00
0c4628677f Fixed profile location interpolation. Generalizing ethnicity, hair color and eye color. 2020-05-19 01:10:32 +02:00
4826ae8571 Added oil tag. Removed Fame Digital from profile config. 2020-05-18 04:28:38 +02:00
003e07491d Added network to profile context when site is available. 2020-05-18 03:34:41 +02:00
885aa4f627 Passing context object with site or network instead of scraper slug and 'site or network' to all profile scrapers. 2020-05-18 03:22:03 +02:00
8733fdc657 Improved actor scraping and display. 2020-05-18 01:22:56 +02:00
af5543190a Showing avatar on release actor tile. Fixed social URL validation. 2020-05-17 05:24:46 +02:00
61965d480c Added birthday to Bang actor scraper. 2020-05-17 05:08:41 +02:00
f42ca7bd52 Added Bang! actor scraper. Fixed date interpolation. Showing date and age of death on profile (only if actor has already died). 2020-05-17 04:59:09 +02:00
7f86399033 Fixed averaging interpolating function. 2020-05-17 03:04:58 +02:00
985ab9d2dc Added profile interpolation. 2020-05-17 03:00:44 +02:00
05ee57378a Storing actor avatars. Using 1 second interval queue for location resolve as per OSM code of conduct. 2020-05-16 04:36:45 +02:00
21d4dd6bfa Scraping and storing actor profiles. 2020-05-15 04:40:59 +02:00
11eb66f834 Switched to tabs. Adding missing actor entries when scraping actors, with batch ID. 2020-05-14 04:26:05 +02:00
f1eb29c713 Added thumb and lazy image scripts. Added FreeOnes and Boobpedia as sites. 2020-05-13 23:17:39 +02:00
dac451bb86 Using alias actor ID when available. Using basic SVG flags instead of PNG. Moved PNG and HD SVG flags to assets. 2020-05-13 20:27:06 +02:00
6040a3f41f Associating actors without network. 2020-05-13 02:56:20 +02:00
5a82e769c7 Fixed dark theme for release tiles. 2020-05-09 02:43:06 +02:00
70594156fd Restored old release tile layout. 2020-05-09 02:17:10 +02:00
f31aef6f5d Replaced container key with route watchers to reduce flashing. 2020-05-09 01:42:10 +02:00
de5b729c0b Changed range filter to routes for every view. 2020-05-09 01:10:07 +02:00
525995615a Changed range toggles to links for home and actor pages. 2020-05-07 03:20:51 +02:00
2b2fb9e3e7 Removed memory usage logs. 2020-05-07 02:16:20 +02:00
c410294022 Added lazy-loading to release tile posters. 2020-05-07 02:10:28 +02:00
af9d8f7858 Drastically improved memory performance media module using streams and temp files. 2020-05-07 01:53:07 +02:00
79c7847f1c Preserving aspect ratio on release tiles. 2020-04-27 04:00:16 +02:00
f684923a8a Actor tiles maintain aspect ratio. 2020-04-27 02:37:30 +02:00
a223f933ce Added 2 minute timeout to media fetch. 2020-04-26 04:21:57 +02:00
2ac879d276 Fixed Vixen scraper, using new token URL for trailers. 2020-04-26 03:51:59 +02:00
2cfbd21560 Adding media streaming experiments. 2020-04-20 23:52:37 +02:00
4b26f8f476 Added covers to media module. Fixed 'New' sorting for all pages. 2020-04-16 22:54:07 +02:00
3b2bf921f3 Replaced batch ranges with 'New' sorting. 2020-04-16 22:23:25 +02:00
e3dfaf4440 Staging release media store by role. 2020-04-16 16:24:59 +02:00
fc58850e56 Added media limit sampling. 2020-04-11 22:49:37 +02:00
cb68319ac0 Added lazy loading to tag photos. Changed tag thumb location. 2020-04-08 14:50:43 +02:00
24b297011e Added various tag posters. 2020-04-02 04:05:55 +02:00
0ad1a5e049 Revert "Added transition to theme switch."
This reverts commit 5d467622f4.
2020-04-02 03:19:45 +02:00
5d467622f4 Added transition to theme switch. 2020-04-02 03:11:00 +02:00
c39bfb234d Blurring video when paused in SFW mode. 2020-04-02 02:23:54 +02:00
baaa701249 Added SFW mode with NSFW warning to releases. 2020-04-02 02:02:03 +02:00
ad7874649f Added self hash filtering to media module. Moved Girl Girl back to Jules Jordan. 2020-04-02 01:10:50 +02:00
9a712e7371 Media module saves files. 2020-03-31 04:05:31 +02:00
4eaacf5697 Expanded new media module. Added network to channel site to fix actor glitch. 2020-03-30 03:01:08 +02:00
a5d859471d Added Jesse Loads Monster Facials. Added various logos. 2020-03-29 23:42:41 +02:00
93d4f0ff1a Added WIP media module. Returning releases from release search database function. Fixed page loop in update module. 2020-03-29 04:00:46 +02:00
6d9f96c5d5 Improved duplicate filtering, now also applying to upcoming updates. Updated Gamma fetchLatest method parameters. Added shortcut for SFW-mode. 2020-03-28 04:37:04 +01:00
95d115b585 Added Aziani. Added URL origin parameter to relevant qu methods. 2020-03-28 01:40:02 +01:00
238ebcbf34 Moved SFW mode to server, added HTTP header trigger. 2020-03-27 17:37:13 +01:00
fb59bf552a Added actor assignment to new actors module. Showing network icon on network-specific actors. Improved dark theme. Changed tag tile design. Added Digital Playground logos. 2020-03-27 04:39:13 +01:00
689dbeefbd Added database structure for piercings. 2020-03-26 03:34:37 +01:00
d29e296799 Added database structure for profiles and tattoos. Improved sidebar appearance. Expanded new actors module. 2020-03-26 03:32:07 +01:00
bb3f6fc408 Added more SFW photos. 2020-03-25 04:25:10 +01:00
1173827a79 Improved dark theme. Changed sidebar toggle icon. 2020-03-25 03:42:46 +01:00
15a386ad05 Added compact sidebar. Added tag sections and posters. 2020-03-25 02:48:54 +01:00
d724f96728 Added new actors module boilerplate. Added tag posters. 2020-03-24 03:48:24 +01:00
4db3da1eaa Removed shadow from network logos. 2020-03-23 05:01:31 +01:00
aa488cc6e3 Changed tags on tag overview. 2020-03-23 04:58:23 +01:00
0e1a760eb3 Removed exclusion from gitignore. 2020-03-23 01:55:42 +01:00
d62ddfd0f2 Temporarily modified .gitignore. 2020-03-23 01:46:20 +01:00
58ead7b426 Added dark and SFW modes. 2020-03-23 01:43:49 +01:00
fdb2b132f6 Improved release storage module. Added new tags module. Added movie scraping. 2020-03-22 03:50:24 +01:00
d765543b30 Improved update runner. Improved HTTP module API, added default user agent. Added PornCZ and Czechav logos. 2020-03-21 02:48:24 +01:00
d53a365fcb 1.112.3 2020-03-19 14:33:34 +01:00
05f24f5e77 Added Lana Rhoades to airtight tag photos. 2020-03-19 14:33:32 +01:00
24fe8a1d8e 1.112.2 2020-03-19 13:16:40 +01:00
2f55596b9d Removed 'scrape' property from sites seed. Corrected Lana Rhoades DAP tag caption. 2020-03-19 13:16:37 +01:00
2c80e77bd7 1.112.1 2020-03-19 02:03:13 +01:00
ba8dfb673f Added 'tunnel' dependency, removed OpenCV. 2020-03-19 02:03:10 +01:00
d37a7ab7cc 1.112.0 2020-03-19 01:55:59 +01:00
deadb3498e Added proxy support to HTTP module. Added Vixen hostnames to default proxy config. 2020-03-19 01:55:52 +01:00
4b310e9dfa Added configurable proxy to HTTP module (also used by qu). Added network and site URL to search documents. 2020-03-19 01:54:25 +01:00
e4b269956e Attaching channel site and studio to stored releases. 2020-03-17 00:58:03 +01:00
0f09fd53eb Refactoring deep scrape. Added tag posters. 2020-03-16 04:10:52 +01:00
c8ebe7892a Refactored update scraper into new module. 2020-03-14 02:56:28 +01:00
4fc0053bd9 Added tag photo directories to repo. 2020-03-13 21:59:44 +01:00
74e5322c5e 1.111.3 2020-03-13 21:54:47 +01:00
8d484ba728 Added tag photos. Auto-adding BTS tag to BAM Visions scenes. 2020-03-13 21:54:44 +01:00
6db48c37fb 1.111.2 2020-03-12 02:35:18 +01:00
8e4573e5ad Retouched BAM Visions logo. 2020-03-12 02:35:14 +01:00
14c2c00d51 1.111.1 2020-03-12 02:22:16 +01:00
01b59f0a9b Changed withReleases boolean to include object in Vixen and Fame Digital scrapers. 2020-03-12 02:21:59 +01:00
370605554b Added profile scraper with scenes to BAM Visions. Passing 'includes' object instead of withReleases boolean to Gamma. 2020-03-12 02:19:45 +01:00
152813730e 1.111.0 2020-03-12 00:59:57 +01:00
25795e2cce Added latest and scene scrapers to BAM Visions. 2020-03-12 00:59:32 +01:00
37e188a0df Added site aliases. Migrated various scrapers to qu. Added BAM Visions base. 2020-03-12 00:15:25 +01:00
c020d5659e 1.110.4 2020-03-11 03:03:21 +01:00
6a950c8b57 Fixed logger in web server. 2020-03-11 03:03:19 +01:00
1f212e1ad6 1.110.3 2020-03-11 03:01:41 +01:00
0d0acb6f3c Fixed release sites for profile scraping. 2020-03-11 03:01:37 +01:00
9c8d914b75 1.110.2 2020-03-10 23:46:59 +01:00
d97b1ab894 Split up profile scrape runner. Fixed wrong search document date key. Added search update CLI. 2020-03-10 23:46:55 +01:00
791a6c1c72 1.110.1 2020-03-10 15:48:21 +01:00
5441063012 Increased compact header logo margin. 2020-03-10 15:48:18 +01:00
071b09709b 1.110.0 2020-03-10 04:42:18 +01:00
db63be8f92 Photo plucker will use discarded photos as fallback. Returning high res photo sources from LegalPorno. 2020-03-10 04:42:15 +01:00
6bfc5e4378 1.109.1 2020-03-10 00:18:06 +01:00
5c55750c0c Fixed qu issues. Fixed media issues. Simplified and expanded date component in search query. 2020-03-10 00:17:57 +01:00
61a795d634 Added 'includes' argument to scrapers to help them avoid unnecessary requests. Added movie actors and movie tags views. 2020-03-09 16:54:45 +01:00
638757b6e4 Added rudimentary movie relations. 2020-03-09 05:06:37 +01:00
8ca98b394f 1.109.0 2020-03-09 02:02:33 +01:00
6cbb7f9c1e Major API change for 'q', renamed to 'qu', refactored modules. Fixed Gamma URL entry ID regex. 2020-03-09 02:02:29 +01:00
7d71cf3a8c 1.108.2 2020-03-08 17:09:35 +01:00
5b4490a845 Showing compact logo on narrow screens. 2020-03-08 17:09:33 +01:00
8211380243 1.108.1 2020-03-08 04:31:22 +01:00
240b203ec0 Realigned header items. 2020-03-08 04:31:19 +01:00
131100d6e6 1.108.0 2020-03-08 04:23:13 +01:00
acad99bdfe Changed q get and geta APIs to include status, refactored scrapers. Showing front- and back-cover on movie tiles and release page (fix). Removed icons from main navigation. Returning scenes from Jules Jordan movie scraper. 2020-03-08 04:23:10 +01:00
b45bb0cfbc 1.107.10 2020-03-07 23:40:44 +01:00
628c5a2013 Fixed Gamma scene scraper extracting the wrong scene ID from non-standard URLs. 2020-03-07 23:40:38 +01:00
d6fb9da176 1.107.9 2020-03-07 04:27:23 +01:00
cd12b53071 Added SexyHub. Added favicon. 2020-03-07 04:27:20 +01:00
5f2c1f3b5c 1.107.8 2020-03-07 02:35:16 +01:00
ff3e956fc7 Added mobile album scraping to Blowpass, improved wrapper. 2020-03-07 02:35:13 +01:00
4773a388ac 1.107.7 2020-03-07 00:32:07 +01:00
ea0e37aa49 Fixed Vivid requesting unavailable HTTPS resource, and breaking on non-existing parameters object. 2020-03-07 00:32:04 +01:00
2791821239 1.107.6 2020-03-06 23:50:55 +01:00
6820608a09 Gamma won't attempt to fetch photos when 'photos' parameter is false. 2020-03-06 23:50:52 +01:00
9d9c741ce9 1.107.5 2020-03-06 23:40:17 +01:00
aad91fe4ae Added Girls Under Arrest to Adult Time. Added mobile album support to some Girlsway sites. Fixed Moms On Moms link. 2020-03-06 23:40:15 +01:00
495f0bcf3b 1.107.4 2020-03-06 23:08:58 +01:00
a579e3c88f Enabled mobile album scraping for Burning Angel and Fame Digital. 2020-03-06 23:08:55 +01:00
6be065c0cb 1.107.3 2020-03-06 19:44:22 +01:00
5d468b58fd Enabled mobile album fetching for Pure Taboo and Fantasy Massage. 2020-03-06 19:44:20 +01:00
1896cd1472 1.107.2 2020-03-06 19:26:26 +01:00
96eef822d6 Allowing mobile parameter to be set on Gamma network. Added mobile to 21Naturals and 21Sextreme, moved 21Sextury mobile to network. 2020-03-06 19:26:23 +01:00
19f46d4d86 1.107.1 2020-03-06 18:59:37 +01:00
5c63bd860b Tags module filters out undefined tags. Gamma will return [] instead of undefined when no tags are available. 2020-03-06 18:59:32 +01:00
3f30f80d34 1.107.0 2020-03-06 04:28:05 +01:00
90172ea19a Added mobile album support to Gamma scraper. 2020-03-06 04:28:01 +01:00
3c14bb26c2 1.106.2 2020-03-06 03:15:52 +01:00
fa7b2a7a23 Fixed subnetwork spacing in network sidebar. Using derived entry IDs for main Hush scrapers. 2020-03-06 03:15:49 +01:00
33e49e1b08 1.106.1 2020-03-06 02:49:58 +01:00
db01599569 Added profile scraper with releases to Hush. Added qtexts to q to return text nodes individually. Including network in profile site. 2020-03-06 02:49:55 +01:00
ae5bd374ba 1.106.0 2020-03-05 23:01:11 +01:00
d90d067659 1.105.5 2020-03-05 23:01:07 +01:00
3889faee26 Added optional sequential scraping and acc release injection. Added Hush Pass and Interracial Pass logos. 2020-03-05 23:01:03 +01:00
6719d805d3 1.105.4 2020-03-05 20:31:29 +01:00
fd6e90e74c Added tour layout scraper to Hush, enabling Interracial POVs, POV Pornstars and See Him Fuck. 2020-03-05 20:31:11 +01:00
0feac66e94 1.105.3 2020-03-05 17:07:13 +01:00
1407700511 Added Interracial Pass sites. Fixed Hush removing poster from base release. 2020-03-05 17:07:07 +01:00
f16251dc78 1.105.2 2020-03-05 05:07:17 +01:00
981b121980 Split Hussie Pass into Hush networks Hush Pass, Hussie Pass and Interracial Pass. 2020-03-05 05:07:14 +01:00
ff54b323e6 1.105.1 2020-03-05 03:44:30 +01:00
f10e4af29b Allowing scrapers to force channel allocation attempt. Added Hush Pass subsite handling to Hussie Pass scraper. 2020-03-05 03:44:27 +01:00
074b281c52 1.105.0 2020-03-05 02:47:56 +01:00
956afa6ae7 Added Hussie Pass scraper. 2020-03-05 02:47:52 +01:00
f3a3ed6369 1.104.4 2020-03-04 17:21:44 +01:00
6c3cba1b87 Added actor photos to Brazzers scene scrape. Added no-video poster to Score. Not flattening actor avatar fallbacks. 2020-03-04 17:21:40 +01:00
6733777f63 1.104.3 2020-03-02 04:19:55 +01:00
b7073361d7 Changed non-existent simple_dict to simple in migration. 2020-03-02 04:19:52 +01:00
65ef021e3e 1.104.2 2020-03-02 04:15:51 +01:00
15af3e91e0 Coalescing shoot ID in search. Added stop words for common TLDs. Sorting tags in search results. 2020-03-02 04:15:47 +01:00
3586d5c745 1.104.1 2020-03-02 03:59:51 +01:00
46d978fe29 Added application avatars. 2020-03-02 03:59:47 +01:00
6e43208778 1.104.0 2020-03-02 03:41:44 +01:00
e79a6b33fb Added 'newly added' filter. Handling paywalled videos in Private scraper. Added shoot ID to search. 2020-03-02 03:41:41 +01:00
d0d3d150ee 1.103.0 2020-03-01 05:28:11 +01:00
e57f440665 Added Amateur Allure. 2020-03-01 05:28:08 +01:00
3290a5f686 Focusing and auto-hiding search tooltip. Separated Girl Girl from Jules Jordan. 2020-03-01 00:21:54 +01:00
a8e14f6305 Improved search query prep. Showing query in header after refresh. 2020-02-29 23:57:45 +01:00
44394ae85d 1.102.9 2020-02-29 22:47:51 +01:00
8dd5925af6 Improved search engine query and added stop words. Added 'secondary' property to tag aliases, for tag aliases to be included in searches and alias lists. 2020-02-29 22:47:48 +01:00
945642c511 1.102.8 2020-02-29 05:00:53 +01:00
b03775fa07 Using generic slugify for MindGeek channel. 2020-02-29 05:00:50 +01:00
870d74a1de 1.102.7 2020-02-29 03:22:55 +01:00
a828fee476 Handling NULL actors and tags in search table query. Added limit parameter to home URL, default to 30. 2020-02-29 03:22:51 +01:00
9c4cc24f42 1.102.6 2020-02-28 03:57:03 +01:00
f1f33080f6 Ignoring undefined video entropy. 2020-02-28 03:56:58 +01:00
1f5b935beb 1.102.5 2020-02-27 05:44:28 +01:00
3dc8547431 Added fake data and Markov experiments. 2020-02-27 05:44:24 +01:00
97cf5b2b6b Added seed file for test data. 2020-02-27 00:38:11 +01:00
bb2fe82c84 1.102.4 2020-02-26 22:33:18 +01:00
3c30e9107a Using dedicated releases search table for ts vector documents. 2020-02-26 22:33:15 +01:00
4910f8650f 1.102.3 2020-02-26 04:10:04 +01:00
51ffcb5be7 Added opt-out trim to capitalize util. 2020-02-26 04:10:01 +01:00
c7fac575e4 1.102.2 2020-02-26 04:07:38 +01:00
82d1f0fd38 Improved mobile layout for header search. Improved release tile layout behavior. 2020-02-26 04:07:35 +01:00
ec70c4f0c7 1.102.1 2020-02-26 03:38:23 +01:00
170cc4244d Using rudimentary full text search. 2020-02-26 03:38:21 +01:00
0b5b9c8aa4 1.102.0 2020-02-26 01:15:56 +01:00
638a71f36c Added rudimentary release search. 2020-02-26 01:15:50 +01:00
756ab09ce6 1.101.1 2020-02-25 22:32:15 +01:00
646ff064a7 Updated Score scraper to accept site and with-releases argument. 2020-02-25 22:32:13 +01:00
82e8ce432b 1.101.0 2020-02-24 04:02:00 +01:00
800a25743d Storing image dimensions and file size to database. Added new site Filthy Femdom to Kink. 2020-02-24 04:01:58 +01:00
f795ccf129 1.100.0 2020-02-24 03:13:00 +01:00
6d1f30f703 Passing matching site to profile scrapers. Allowing scrapers to pass avatar metadata. Added scraper and copyright properties to media. Auto-adding copyright from site or scraper to avatars. Separated Porn Pros from Whale Member. 2020-02-24 03:12:58 +01:00
73443b77a8 1.99.7 2020-02-24 00:31:39 +01:00
0ae7d2669a Added profile scraping to Private. 2020-02-24 00:31:36 +01:00
70afe75eb4 1.99.6 2020-02-23 22:37:17 +01:00
164681427a Fixed Kelly Madison posters, marking frontpage video as teaser. 2020-02-23 22:37:14 +01:00
21688ab9d0 1.99.5 2020-02-23 22:26:18 +01:00
0d719d88ea Removed Boob Pedia as gender source, unreliable. 2020-02-23 22:26:14 +01:00
5acaa9c6bf Marked Metro HD sites as 'native' as to use site URL. 2020-02-23 22:07:35 +01:00
c76320b078 1.99.4 2020-02-23 22:01:14 +01:00
8359f78e2e Fixed RK scraper returning dick size as bust size. 2020-02-23 22:01:12 +01:00
b9c8950f6d 1.99.3 2020-02-23 05:23:10 +01:00
968eb07472 Improved actor filter layout behavior. 2020-02-23 05:23:07 +01:00
95ed67b1fe 1.99.2 2020-02-23 05:00:12 +01:00
0ede7e0f82 Added various Porn Pros archive sites. 2020-02-23 05:00:08 +01:00
5afd86a932 1.99.1 2020-02-22 23:50:28 +01:00
74274f879e Re-added image entropy filter. 2020-02-22 23:50:25 +01:00
06fa428790 1.99.0 2020-02-22 23:25:13 +01:00
915eb75719 Refactored Vixen scraper, using API endpoint and added actor profile and releases scraper. Release scraper will return base release when present and 'deep' argument is false. 2020-02-22 23:25:10 +01:00
d977a5e712 1.98.1 2020-02-22 05:29:07 +01:00
5e1a1005f1 Refactored Vixen scene scraper. Using better poster source for Vixen. Returning video as teaser instead of trailer. 2020-02-22 05:29:02 +01:00
c8c638d201 1.98.0 2020-02-22 04:37:53 +01:00
e5c6ccd252 Scraping upcoming Vixen scenes. Fetching release media groups sequentially to prevent collisions. 2020-02-22 04:37:48 +01:00
3c92e828f6 1.97.0 2020-02-22 03:22:32 +01:00
349a5a506e Queueing and batching media HTTP requests for improved reliability. 2020-02-22 03:22:30 +01:00
b2dfbac9e5 1.96.0 2020-02-21 03:52:16 +01:00
b4e69d46ae 1.95.3 2020-02-21 03:52:13 +01:00
13a8221b92 Added New Sensations. Returning null from q's date formatter when date is invalid. 2020-02-21 03:51:57 +01:00
111fc8ad00 1.95.2 2020-02-20 22:27:04 +01:00
7ac5a8e08c Catching media failures per batch. Refined teaser logging. 2020-02-20 22:27:00 +01:00
278246a343 1.95.1 2020-02-20 02:53:27 +01:00
acf780635b Release tile favicon now links to network overview. 2020-02-20 02:53:24 +01:00
5d212df929 1.95.0 2020-02-20 02:35:25 +01:00
377970f874 Added parent-child relations to network, showing parent network in sidebar. Added Burning Angel using Gamma API. 2020-02-20 02:35:23 +01:00
6b358d74db 1.94.2 2020-02-19 04:50:13 +01:00
2c490753a4 Removed stray console log from Gamma scraper. 2020-02-19 04:50:10 +01:00
9c234af747 1.94.1 2020-02-19 04:49:57 +01:00
8889ea5cf3 Using hasTeaser param instead of date comparison to skip upcoming release teasers in Gamma scraper. 2020-02-19 04:49:54 +01:00
372ff54261 Added teaser source to Gamma scraper. Not adding Gamma teaser for upcoming releases. 2020-02-19 04:47:20 +01:00
6c942f5a95 1.94.0 2020-02-19 04:41:56 +01:00
97f5e49187 Refactored media module. Returning 320p and 720p videos from MindGeek as teasers instead of trailers. 2020-02-19 04:41:53 +01:00
b9e617edfc 1.93.5 2020-02-18 16:05:18 +01:00
86a2c8f0f5 Re-added background to Porn Pros network logo. 2020-02-18 16:05:15 +01:00
c69ed23b1c 1.93.4 2020-02-18 16:00:39 +01:00
40bf476ea6 Fixed Porn Pros scraper. Added various Score site logos. 2020-02-18 16:00:36 +01:00
cabae4989e 1.93.3 2020-02-15 02:47:54 +01:00
d1532e1bec Fixed scene URLs for Full Porn Network. 2020-02-15 02:47:50 +01:00
850509257d 1.93.2 2020-02-15 02:06:23 +01:00
30e58fb397 Added actor description to Full Porn Network profile scraper. 2020-02-15 02:06:21 +01:00
d69e1a1231 1.93.1 2020-02-15 02:04:49 +01:00
b6fe91b016 Added profile avatar and release scraping to Full Porn Network. 2020-02-15 02:04:46 +01:00
958c2b625c 1.93.0 2020-02-15 01:50:26 +01:00
880cc64022 Added Full Porn Network scraper. 2020-02-15 01:50:22 +01:00
8389787c7e 1.92.1 2020-02-15 01:13:13 +01:00
c5b978dfae Added site search function rollback to migration. 2020-02-15 01:13:10 +01:00
8f0f5131c8 1.92.0 2020-02-15 01:10:36 +01:00
fe3cc901b0 Added PG site search function and enabled site search bar. Added Full Porn network assets. 2020-02-15 01:10:32 +01:00
bf5a2096f5 1.91.4 2020-02-14 02:30:46 +01:00
872a529d3e Showing actors without gender info in overview. 2020-02-14 02:30:43 +01:00
6a0babe986 1.91.3 2020-02-13 23:05:31 +01:00
5e461f1f1a Added proper Gamma parameters to 21Sextury sites. 2020-02-13 23:05:28 +01:00
14b8993d10 1.91.2 2020-02-13 15:37:27 +01:00
5b19fdf7f2 Fixed photo banner on tags. 2020-02-13 15:37:25 +01:00
f33981ef34 1.91.1 2020-02-13 04:11:35 +01:00
1ff8d37d89 Added upcoming support to Nubiles. Renamed q's formatDate to extractDate, added actual formatDate. 2020-02-13 04:11:32 +01:00
bbf06a3882 1.91.0 2020-02-13 03:44:07 +01:00
c9c7a33585 Added Cherry Pimps/Pimp.XXX. 2020-02-13 03:44:04 +01:00
d4a42bff90 1.90.1 2020-02-12 23:49:25 +01:00
b7abd805e2 Added Girl Girl to Jules Jordan. 2020-02-12 23:49:22 +01:00
d79c6aee7f 1.90.0 2020-02-12 23:00:34 +01:00
cd2ca65903 Added Nubiles network. 2020-02-12 23:00:32 +01:00
2b5b8fb19d 1.89.2 2020-02-12 16:26:13 +01:00
98d29c0af0 Added Insex fallback for Paintoy. 2020-02-12 16:26:08 +01:00
2b90a7a5c8 1.89.1 2020-02-12 04:59:18 +01:00
48b10d0f49 Using brief mode on Insex. 2020-02-12 04:59:15 +01:00
e22b09700d 1.89.0 2020-02-12 04:40:01 +01:00
b8074205ef Added Insex. Renamed q's stand-alone date function. Separated q's trim function. Release tile uses cover if available, and poster is not available. 2020-02-12 04:39:57 +01:00
2f894edda5 1.88.1 2020-02-12 02:19:46 +01:00
fa30366ea4 Moved site tags from tag seed to site seed. 2020-02-12 02:19:43 +01:00
86eba83fd3 1.88.0 2020-02-12 01:54:58 +01:00
2f70de8e11 Added preflight method to scrapers. Added Assylum. 2020-02-12 01:54:54 +01:00
bec26ee072 1.87.1 2020-02-11 19:05:14 +01:00
df4a2b0bb3 Fetching old scene links from Vivid search API. 2020-02-11 19:05:12 +01:00
4caf0dbe95 1.87.0 2020-02-11 04:58:21 +01:00
dd6a1d9bfd Added Vivid network. Added ASMR Fantasy to Adult Time. Storing deep URL in database. Added href to header links. 2020-02-11 04:58:18 +01:00
114e2e03b2 1.86.0 2020-02-10 23:11:15 +01:00
ce448da7e0 Added 21 Naturals and 21 Sextreme sites. 2020-02-10 23:11:11 +01:00
c411979edb 1.85.2 2020-02-10 02:05:30 +01:00
4c7c3c2ff4 Using router-link slots for header link indicators. Added release cache to store. 2020-02-10 02:05:28 +01:00
2f66e36c28 1.85.1 2020-02-10 01:27:28 +01:00
48b1f15070 Restoring scroll position when going from home to scene to home. 2020-02-10 01:27:13 +01:00
f5b60ac743 1.85.0 2020-02-09 23:25:56 +01:00
139f0ce7cb Allowing release scrapers to return actor details. Added True Amateurs. 2020-02-09 23:25:54 +01:00
739d2de297 1.84.1 2020-02-09 19:41:42 +01:00
0f513266a0 Added Black for Wife to JayRock. Switched parameters field to JSON type. 2020-02-09 19:41:39 +01:00
f7f4da24ec 1.84.0 2020-02-09 05:30:13 +01:00
4eb57b4a91 Added actor alphanetic and gender filters. 2020-02-09 05:30:10 +01:00
cde290fbd7 1.83.4 2020-02-09 03:09:09 +01:00
9d9eda29be Added scene count to actor inspect. Preferring network slug over data brand for scene URLs in MindGeek scraper, since milehighmedia.com's brand is milehigh, resulting in milehigh.com. 2020-02-09 03:09:06 +01:00
2068202ca6 1.83.3 2020-02-09 02:01:42 +01:00
e61ed2bb5f Added 21Sextreme and 21Naturals networks. Scraping all actor release pages for Brazzers. 2020-02-09 02:01:39 +01:00
885f51943a 1.83.2 2020-02-08 05:16:08 +01:00
8ca458a8ff Added Moms on Moms to Girlsway. 2020-02-08 05:16:04 +01:00
a1b9b7ab38 1.83.1 2020-02-08 04:52:36 +01:00
d2cb74a252 Added Fantasy Massage sites. Improved Private scraper, added movie link. 2020-02-08 04:52:32 +01:00
ff8ab2fe09 1.83.0 2020-02-08 02:49:42 +01:00
1546e0836c Split Girlsway from Adult Time. Added Fantasy Massage. Using Gamma scraper for Pure Taboo. Added photo path parameter to Gamma scraper. 2020-02-08 02:49:39 +01:00
bfb26b717a 1.82.0 2020-02-07 19:53:20 +01:00
5ba308f07a Added Adult Time. Adding context to logger. 2020-02-07 19:53:16 +01:00
9e501426cb 1.81.0 2020-02-07 04:46:54 +01:00
6b56bad21e Added Twistys. 2020-02-07 04:44:01 +01:00
59a26ea00b 1.80.6 2020-02-07 03:40:14 +01:00
30963b94dd Added profile releases for classic Fame Digital sites (Silvia Saint and Silverstone DVD). 2020-02-07 03:40:11 +01:00
49405b953f 1.80.5 2020-02-07 01:59:48 +01:00
be5dd4acd8 Added actor release URL resolver for Peter North on Fame Digital. 2020-02-07 01:59:43 +01:00
971cda1cb5 1.80.4 2020-02-07 01:48:24 +01:00
ea9c2dfe67 Scraping all actor release pages for Gamma. Improved actor matching for Gamma API. 2020-02-07 01:48:21 +01:00
5fc56308d2 1.80.3 2020-02-07 01:06:44 +01:00
631ac34573 Blowpass now uses Gamma module for latest and upcoming. 2020-02-07 01:06:39 +01:00
8a139e1ac0 1.80.2 2020-02-06 23:59:34 +01:00
be83505ecf Added channel support to Gamma scene scraper. 2020-02-06 23:59:32 +01:00
2f9c2332f9 1.80.1 2020-02-06 23:52:03 +01:00
44f0064b42 Cleaned up Gamma profile scraper. 2020-02-06 23:52:00 +01:00
65f98c6387 Refactored 21Sextury module to use Gamma API. 2020-02-06 23:51:13 +01:00
ee8994582c 1.80.0 2020-02-06 23:15:31 +01:00
6e1de52a40 Added Fame Digital. Added actor release scraping to DDF Network. Improved q and Gamma scraper. 2020-02-06 23:15:28 +01:00
db14eaa5f9 1.79.4 2020-02-06 00:22:34 +01:00
054dfba6b5 Fixed Score module to scrape releases for actors with just one page. 2020-02-06 00:22:31 +01:00
ed1bc6c73f 1.79.3 2020-02-05 23:57:57 +01:00
d4801bb240 Returning window.document instead of element as document from q. Fixed actor collisions when scrapers return same scene multiple times. Scraping all Score actor release pages. Fixed 21Sextury and PureTaboo photo scraping. 2020-02-05 23:57:55 +01:00
75dbe2548a 1.79.2 2020-02-05 01:41:34 +01:00
38bab672d4 Better handling of empty profiles. Modified Score scraper for Big Tit Terry Nova. Improved Naughty America and various other logos. 2020-02-05 01:41:30 +01:00
aa7dca65d1 1.79.1 2020-02-04 04:07:53 +01:00
e6dbb60e4a Added various higher quality logos. 2020-02-04 04:07:49 +01:00
a43be4cbdb 1.79.0 2020-02-04 03:12:12 +01:00
f921bb4ae9 Generating and using URL slugs for releases, improver slugify module. Added 'extract' parameter to MindGeek scraper to get scenes not associate with a channel (see Digital Playground). Added various high res logos. 2020-02-04 03:12:09 +01:00
ca33704f51 1.78.0 2020-02-04 00:18:56 +01:00
ef602a3a15 Added basic filename copy. Added HTTP helper to q. Fetching all actor release pages from Naughty America. Added various high res network logos. 2020-02-04 00:18:53 +01:00
bffa6d2c9e 1.77.7 2020-02-03 02:57:56 +01:00
5bae5b6e5f Adapted Score scraper for 18eighteen, 40 Something Mag and 50 Plus Milfs. Updated Score network logo and favicon. 2020-02-03 02:57:53 +01:00
dfa0183669 1.77.6 2020-02-03 02:12:04 +01:00
944f091ca5 Adapted Score scraper for Scoreland2. 2020-02-03 02:12:02 +01:00
b20cf53645 1.77.5 2020-02-03 02:05:00 +01:00
a671190fff Adapted Score scraper for Score Classics. 2020-02-03 02:04:47 +01:00
b075adf424 1.77.4 2020-02-03 00:51:28 +01:00
a96680875c Fixed media duplicate fallbacks being ignored. 2020-02-03 00:51:25 +01:00
b4c0b9f8eb 1.77.3 2020-02-03 00:39:47 +01:00
a45bebddac Adapter Score scraper for Score Videos. 2020-02-03 00:39:43 +01:00
6f5ba925c1 1.77.2 2020-02-02 22:36:36 +01:00
0ed1b2eff9 Added generic photo page extract method to media module, to allow pre-filtering sources and relief Dogfart scraper. Added 'transsexual' site tag to Trans Angels. 2020-02-02 22:36:33 +01:00
204a4d4bdd 1.77.1 2020-02-02 15:26:27 +01:00
10d2ba78d0 Fixed Score searching for actors infinitely. 2020-02-02 15:26:24 +01:00
96e027b4e3 1.77.0 2020-02-02 05:15:03 +01:00
a97c6defca Added teaser support. Added Score network with scraper for Scoreland. Improved q. Added assets. 2020-02-02 05:14:58 +01:00
14e5695b6e 1.76.0 2020-02-01 04:42:38 +01:00
87e2d6bbfd Added actor releases to MindGeek module. 2020-02-01 04:42:35 +01:00
8ba6a82065 1.75.0 2020-02-01 04:14:11 +01:00
cde9aba0cb Redundant actor sources can now be bundled in configuration. Fixed Men network actor path. 2020-02-01 04:14:08 +01:00
5ff916475a 1.74.3 2020-02-01 03:30:14 +01:00
09ea3125df Fixed release merge order. 2020-02-01 03:30:11 +01:00
46cadcb8e5 1.74.2 2020-02-01 03:03:01 +01:00
abf9893f9d Fixed web server plugins. 2020-02-01 03:02:58 +01:00
7930fbce49 1.74.1 2020-02-01 02:26:03 +01:00
3541a9c402 Integrated Blowpass into generic Gamma scraper. 2020-02-01 02:26:00 +01:00
5dfaa4c126 1.74.0 2020-02-01 01:15:43 +01:00
94bf207397 Added Wicked network. Merged Evil Angel, XEmpire and Wicked into generic Gamma scraper. 2020-02-01 01:15:40 +01:00
37ab07356e 1.73.0 2020-01-31 21:43:18 +01:00
b7f51a8deb Added avatar and actor releases to Bang Bros scraper. 2020-01-31 21:43:16 +01:00
c8671afe47 1.72.0 2020-01-31 19:25:45 +01:00
504bcd02e3 Added Naughty America profile and actor releases scraper. 2020-01-31 19:25:42 +01:00
c882862af6 1.71.1 2020-01-31 02:01:33 +01:00
83164e44d3 Fixed site search overflowing networks page. 2020-01-31 02:01:30 +01:00
f8a15be565 1.71.0 2020-01-31 01:55:58 +01:00
ffdf0690e7 Added option to fetch all of an actor's releases (for supporter scrapers), and a utility to extract posters. 2020-01-31 01:55:55 +01:00
4ecb386233 1.70.0 2020-01-31 00:39:53 +01:00
2fef4b4314 Fetching and returning releases for Evil Angel actors. 2020-01-31 00:39:49 +01:00
4012669a3e 1.69.0 2020-01-31 00:25:55 +01:00
3f113310e3 Added Trans Angels to MindGeek. Interpreting MindGeek 'other' gender as transsexual. 2020-01-31 00:25:51 +01:00
54ef0ff807 1.68.4 2020-01-31 00:13:00 +01:00
c48d04de0f Set page title for networks and tags overviews. 2020-01-31 00:12:58 +01:00
aab304dd20 1.68.3 2020-01-31 00:09:13 +01:00
c9200180cf Tag tiles use SPA links instead of anchors. 2020-01-31 00:09:11 +01:00
e9dfeba11f 1.68.2 2020-01-31 00:03:07 +01:00
2035b06888 Actor tiles use SPA links instead of anchors. 2020-01-31 00:03:05 +01:00
64249a06ac 1.68.1 2020-01-30 23:44:21 +01:00
b3f3455237 Restored rudimentary tag filter for tag view. 2020-01-30 23:44:18 +01:00
6ca3ad4c63 1.68.0 2020-01-30 23:42:29 +01:00
42f227c39d Restored rudimentary tag filtering. 2020-01-30 23:41:10 +01:00
eb9bc4677e 1.67.2 2020-01-30 04:15:13 +01:00
2458b91ac2 Removed trailer debug log. 2020-01-30 04:15:09 +01:00
a91cfe7ddc 1.67.1 2020-01-30 04:14:54 +01:00
0b4a1a5232 Moved all preferred resolutions to config. 2020-01-30 04:14:51 +01:00
d9623789bd 1.67.0 2020-01-30 03:53:59 +01:00
c300d4d251 Added trailer support to Vogov. Set preferred trailer resolution to 480p. Added 480p and other resolution trailers support to XEmpire. 2020-01-30 03:53:54 +01:00
44c44cf9e6 1.66.0 2020-01-30 01:14:36 +01:00
ff61094b69 Added Men network and Icon Male to MindGeek. Added entropy filter to media module to help filter out generic avatars. Added Pure Taboo. Various logo updates. 2020-01-30 01:14:31 +01:00
04e9d29010 1.65.3 2020-01-29 04:02:35 +01:00
d61ea26eb5 Added 'ignore' parameter to sites. Added light logo for Metro HD. 2020-01-29 04:02:32 +01:00
000105b175 1.65.2 2020-01-29 03:19:43 +01:00
8b840f52d1 Added description property to LegalPorno scraper (rarely used, see GIO337). 2020-01-29 03:19:38 +01:00
769e7bb37e 1.65.1 2020-01-29 02:31:58 +01:00
81ede3f511 Improved MindGeek avatar fix. 2020-01-29 02:31:55 +01:00
9c00216e62 1.65.0 2020-01-29 02:24:23 +01:00
fc675ae144 Added Metro HD network using MindGeek scraper. Fixed MindGeek profile scraper avatar issue. 2020-01-29 02:24:19 +01:00
b75780044c 1.64.0 2020-01-28 03:05:55 +01:00
76852daf6d Added VogoV (no trailer yet). Fixed MindGeek profile scraper. 2020-01-28 03:05:53 +01:00
601196ddeb 1.63.0 2020-01-27 22:54:16 +01:00
6d4fd5fd77 Added MindGeek profile scraper for all MG sites. 2020-01-27 22:54:14 +01:00
24fe61e064 1.62.10 2020-01-27 03:07:09 +01:00
32a0188b72 Fixed Boobpedia scraper. Catching non-OK responses for Vixen scraper. 2020-01-27 03:07:06 +01:00
77b214f1dc 1.62.9 2020-01-27 01:54:52 +01:00
345103d759 Fixed ;. 2020-01-27 01:54:42 +01:00
89c0776045 Merge branch 'master' of localhost:pendulum/traxxx 2020-01-27 00:41:21 +00:00
eca65f6b4d Inspecting performance. 2020-01-27 00:41:04 +00:00
6b493f11d7 1.62.8 2020-01-26 23:10:11 +01:00
c83c5513df Fixed compact actor view and gender icon size. 2020-01-26 23:10:08 +01:00
2c097ca41f 1.62.7 2020-01-25 06:25:40 +01:00
4387092f7d Changed default thumbnail quality to 100. 2020-01-25 06:25:33 +01:00
72817f7be3 1.62.6 2020-01-25 03:27:18 +00:00
68f9043910 Not fetching basic actors when actor names are supplied. Set default media directory to ./media. 2020-01-25 03:26:49 +00:00
0ad2952563 1.62.5 2020-01-25 02:30:01 +01:00
2389c982bb Showing gender on release actor tiles. 2020-01-25 02:29:49 +01:00
7d0cf0f100 1.62.4 2020-01-25 01:47:02 +01:00
ab82329171 Improved q so missing date element returns null. 2020-01-25 01:46:58 +01:00
3891a6e86b 1.62.3 2020-01-25 01:26:17 +01:00
bceded3ebd Added gender indicator to actor tiles. Fixed PornHub scraper fetching default avatars. 2020-01-25 01:26:13 +01:00
4d53b84587 1.62.2 2020-01-24 23:46:48 +01:00
37e4259fbd Changed profile height to max-height. 2020-01-24 23:46:44 +01:00
11839 changed files with 371741 additions and 47044 deletions

View File

@@ -5,7 +5,7 @@ root = true
[*]
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_style = tab
indent_size = 4
# Matches multiple files with brace expansion notation

View File

@@ -1 +0,0 @@
src/scrapers/template.js

View File

@@ -2,27 +2,26 @@
"root": true,
"extends": ["airbnb-base", "plugin:vue/recommended"],
"parserOptions": {
"parser": "babel-eslint",
"parser": "@babel/eslint-parser",
"ecmaVersion": 2019,
"sourceType": "module"
},
"rules": {
"indent": ["error", "tab"],
"no-tabs": "off",
"no-unused-vars": ["error", {"argsIgnorePattern": "^_"}],
"no-console": 0,
"indent": "off",
"template-curly-spacing": "off",
"max-len": [2, {
"code": 300,
"tabWidth": 4,
"ignoreUrls": true
}],
"vue/html-indent": ["error", 4],
"max-len": 0,
"vue/no-v-html": 0,
"vue/html-indent": ["error", "tab"],
"vue/multiline-html-element-content-newline": 0,
"vue/singleline-html-element-content-newline": 0,
"vue/multi-word-component-names": 0,
"no-param-reassign": ["error", {
"props": true,
"ignorePropertyModificationsFor": ["state", "acc"]
}],
}]
},
"globals": {
"CONFIG": true

4
.gitignore vendored
View File

@@ -1,9 +1,13 @@
node_modules/
dist/
log/
media/
html/
public/js/*
public/css/*
config/*
!config/default.js
assets/js/config/
!assets/js/config/default.js
*.heapprofile
*.heapsnapshot

2
.nvmrc
View File

@@ -1 +1 @@
12.13.0
16.13.0

524
README.md
View File

@@ -2,367 +2,195 @@
**NSFW** - The latest releases from your favorite porn studios in one place.
## Install & run
Use [nvm](https://github.com/creationix/nvm) to install a recent version of NodeJS. Download and unpack or clone this repository, and run the following in the root directory:
Use [nvm](https://github.com/creationix/nvm) to install NodeJS v14.15.4 or newer. Download and unpack or clone this repository, and run the following in the root directory:
`npm install`
### Set up database
Install PostgreSQL, make sure password authentication is enabled (scram-sha-256) and create a database with a privileged user.
### Configuration
Do not modify `config/default.js`, but instead create a copy at `config/local.js` containing the properties you wish to change. If you have set `NODE_ENV`, copy `assets/js/config/default.js` to `assets/js/config/[environment].js`. After setting up PostgreSQL and configuring the details, run the following commands to create and populate the tables, and build the project:
`npm run migrate`
`npm run seed`
`npm start`
You can also use `npm run flush` to run both steps at once, and wipe the database completely later.
### Configuration
Do not modify `config/default.js`, but instead create a copy at `config/local.js` containing the properties you wish to change.
#### Networks and channels
To scrape the networks and channels available in the database, you can configure `include` and `exclude` lists. To include all available channels and only use the `exclude` list, leave the `include` parameter unconfigured. The `exclude` lists will exclude channels and child networks from networks on the `include` lists, but not vice versa. That is, if the `include` list includes a network and the `exclude` list excludes one of that network's channels, the channel will not be scraped. However, if the `include` list includes a channel, and the `exclude` list includes its parent network, the channel will be scraped.
### Options
`npm start -- --option value`
This configuration will scrape Evil Angel and all XEmpire channels, except for LesbianX.
```
include: {
networks: [
'xempire',
],
channels: [
'evilangel',
],
},
exclude: {
channels: [
'lesbianx',
],
}
```
Running `npm start` without any arguments will run the web server.
This configuration will scrape all channels, except for BAM Visions, and except all channels part of the Vixen network.
```
exclude: {
channels: [
'bamvisions',
],
networks: [
'vixen'
],
},
```
* `--fetch`: Fetch updates instead of running the webserver. Without further arguments, it will use the networks and sites defined in the configuration file.
* `--site [site ID]`: Fetch updates from a specific site. The site ID is typically the site name in lowercase and without cases or special characters. For example, Teens Like It Big is teenslikeitbig.
* `--network [network ID]`: Fetch updates from all sites of a specific network. The network ID is composed similarly to the site ID.
* `--after "[time]"`: Do not fetch scenes older than this. Example values are: `"1 month"`, `"2 weeks"`, `"3 years"`.
* `--scene [URL]`: Try to retrieve scene details from its official site or network URL.
* `--deep`: Follow each release link found running `--site` or `--network` and scrape it for more details. Enabled by default at the moment of writing; use `--no-deep` to only save information found on the overview pages.
* `--copy`: Try to copy relevant results to the clipboard. When used with `--scene`, it will copy the filename as defined in the config with all the details filled in.
### Building
To build traxxx, run the following command:
#### Developer options
`npm run build`
To generate thumbnails for new logos and tag photos, install ImageMagick and run:
`npm run logos-thumbs`
`npm run tags-thumbs`
### Run
`./traxxx --option value` or `npm start -- --option value`
* `--server`: Run the web server
#### Channels
* `--channels [slug] [slug]`: Fetch updates from specific channels. The slug is the channel's name in lowercase and without cases or special characters. For example, Teens Like It Big is teenslikeitbig. Overrides configured included networks and channels.
* `--networks [slug] [slug]`: Fetch updates from all sites of a specific network. The network slug is composed similarly to the channel slug. Overrides configured included networks and channels.
* `--exclude-channels [slug] [slug]`: Scrape every configured, specified or available channel, except for specified. Overrides configured excluded channels.
* `--exclude-networks [slug] [slug]`: Scrape every configured, specified or available network, except for specified. Overrides configured excluded networks.
* `--after "[time]"`: Do not fetch scenes older than this period or date. Example values are: `"1 month"`, `"3 years"`, `"2019-01-01"`.
* `--scene [URL]`: Try to retrieve scene details from its official channel or network URL.
* `--deep`: Follow each release link found running `--channel` or `--network` and scrape it for more details. Enabled by default ; use `--no-deep` to only save information found on the overview pages.
#### Actors
* `--actors "[name]" "[name]"`: Fetch actor profiles. When no names are specified, actors without existing profiles are scraped
* `--actors-file [filepath]`: Fetch all scenes for the actors specified in a file using a newline delimiter.
* `--actors-sources [slug] [slug]`: Scrapers to use for actor profiles. Defaults to config.
* `--actors-update [time]`: Update actors that don't have any profiles newer than period ("1 month") or date (2020-08-01). Using this argument without a value will default to 1900-01-01, practically updating all actors.
* `--actors-scenes`: Fetch all scenes for scraped actors. Use with caution, as an actor may have many scenes.
* `--scene-actors`: Fetch profiles for actors associated with scraped scenes. Use with caution, as scenes may have many actors, each with many profiles.
#### Developers
* `--no-save`: Do not store retrieved information in local database, forcing re-fetch.
* `--debug`: Show full error stack trace.
* `--level`: Change log level to `silly`, `verbose`, `info`, `warn` or `error`.
* `--delete-scenes`: Delete scenes and assets by ID.
* `--delete-movies`: Delete movies and assets by ID.
* `--flush-network`: Delete all scenes and movies with assets from all network channels
* `--flush-channel`: Delete all scenes and movies with assets from a channel
* `--flush-batch`: Delete all scenes and movies with assets from a batch ID
* `--flush-media`: Delete temporary media directory and all unassociated media entries and files
### API
A GraphQL API is available at `/graphql`, and a REST API is available at the following `GET` endpoints:
* `/api/scenes`: Fetch the latest releases. Supports search with `query` or `q` parameter;
* `/api/scenes/{ID}`: Fetch scene by ID.
* `/api/actors`: Fetch actors. Search `query` or `q` parameter required.
* `/api/actors/{ID|slug}`: Fetch detailed actor by ID or slug.
* `/api/entities`: Fetch networks and channels. Use the `type` parameter to filter for either `channel`s or `network`s.
* `/api/entities/{ID|slug}`: Fetch detailed network or channel by ID. To fetch by slug, the `type` parameter must specify either `channel` or `network`.
* `/api/channels`: Fetch channel entities. Supports the `q` or `query` parameter for searching.
* `/api/channels/{ID|slug}`: Fetch detailed channel by ID or slug.
* `/api/networks`: Fetch networks. Supports a `q` or `query` parameter for searching.
* `/api/networks/{ID|slug}`: Fetch detailed network by ID or slug.
* `/api/tags`: Fetch tags.
* `/api/tags/{ID|slug|name}`: Fetch detailed tag by ID, slug or name.
## Supported networks & sites
285 sites on 15 networks, continuously expanding!
* **21Sextury**
* Anal Teen Angels
* Asshole Fever
* Butt Plays
* Club Sandy
* DP Fanatics
* Deepthroat Frenzy
* Footsie Babes
* Gapeland
* Lez Cuties
* Pix and Video
* **Bang Bros**
* Ass Parade
* AvaSpice
* Back Room Facials
* Backroom MILF
* Ball Honeys
* Bang Bus
* Bang Casting
* Bang POV
* Bang Tryouts
* BangBros 18
* BangBros Angels
* BangBros Remastered
* Bangbros Clips
* Big Mouthfuls
* Big Tit Cream Pie
* Big Tits, Round Asses
* BlowJob Fridays
* Blowjob Ninjas
* Boob Squad
* Brown Bunnies
* Can He Score?
* Casting
* Chongas
* Colombia Fuck Fest
* Dirty World Tour
* Dorm Invasion
* Facial Fest
* Fuck Team Five
* Glory Hole Loads
* Latina Rampage
* Living With Anna
* MILF Lessons
* Magical Feet
* Milf Soup
* MomIsHorny
* Monsters of Cock
* Mr CamelToe
* Mr. Anal
* My Dirty Maid
* My Life In Brazil
* Newbie Black
* Party of 3
* Pawg
* Penny Show
* Porn Star Spa
* Power Munch
* Public Bang
* Slutty White Girls
* Stepmom Videos
* Street Ranger
* Tugjobs
* Working Latinas
* **Blowpass**
* 1000 Facials
* Immoral Live
* Mommy Blows Best
* Only Teen Blowjobs
* Throated
* **Brazzers**
* Asses In Public
* Baby Got Boobs
* Big Butts Like It Big
* Big Tits In Sports
* Big Tits In Uniform
* Big Tits at School
* Big Tits at Work
* Big Wet Butts
* Brazzers Exxtra
* Brazzers Live
* Brazzers Vault
* Brazzers en Español
* Busty & Real
* Bustyz
* Butts & Blacks
* CFNM
* Day With A Pornstar
* Dirty Masseur
* Doctor Adventures
* Hot And Mean
* Hot Chicks Big Asses
* JugFuckers
* Milfs Like it Big
* Mommy Got Boobs
* Moms in Control
* Pornstars Like it Big
* Racks & Blacks
* Real Wife Stories
* SexPro Adventures
* Shes Gonna Squirt
* Teens Like It Big
* Teens Like It Black
* ZZ Series
* **DDF Network**
* 1By-Day
* DDF Busty
* DDF Network VR
* Euro Girls on Girls
* Euro Teen Erotica
* Hands on Hardcore
* Hot Legs and Feet
* House of Taboo
* Only Blowjob
* **Dogfart Network**
* Barb Cummings
* Black Meat White Feet
* Blacks On Blondes
* Blacks On Boys
* Blacks On Cougars
* Candy Monroe
* Cuckold Sessions
* Cumbang
* Dogfart Behind The Scenes
* Glory Hole
* Gloryholes And Handjobs
* Gloryholy Initiations
* Interracial Blowbang
* Interracial Pickups
* Katie Thomas
* Ruth Blackwell
* Spring Thomas
* The Minion
* Watching My Daughter Go Black
* Watching My Mom Go Black
* We Fuck Black Girls
* Wife Writing
* Zebra Girls
* **Evil Angel**
* **Jules Jordan**
* **Kink**
* 30 Minutes of Torment
* Bound Gangbangs
* Bound Gods
* Bound in Public
* Brutal Sessions
* Butt Machine Boys
* Device Bondage
* Devine Bitches
* Electrosluts
* Everything Butt
* Families Tied
* Foot Worship
* Fucked and Bound
* Fucking Machines
* Hardcore Gangbang
* Hogtied
* Kink University
* Men In Pain
* Men on Edge
* Naked Kombat
* Public Disgrace
* Sadistic Rope
* Sex and Submission
* The Training of O
* The Upper Floor
* TS Pussy Hunters
* TS Seduction
* Ultimate Surrender
* Water Bondage
* Whipped Ass
* Wired Pussy
* **LegalPorno**
* **Mike Adriano**
* All Anal
* True Anal
* Nympho
* Swallowed
* **MOFOS**
* Blogs
* Don't Break Me
* Ebony Sex Tapes
* Girls Gone Pink
* I Know That Girl
* Latina Sex Tapes
* Lets Try Anal
* MOFOS Lab
* Mofos B Sides
* Pervs On Patrol
* Public Pickups
* Real Slut Party
* Share My BF
* She's A Freak
* Stranded Teens
* **Naughty America**\*
* 2 Chicks Same Time
* American Daydreams
* Anal College
* Asian 1 On 1
* Ass Masterpiece
* Big Cock Bully
* Diary of a Milf
* Diary of a Nanny
* Dirty Wives Club
* Fast Times
* Housewife 1 on 1
* I Have a Wife
* LA Sluts
* Latin Adultery
* Latina Step Mom
* Lesbian Girl on Girl
* Live Gym Cam
* Live Naughty Milf
* Live Naughty Nurse
* Live Naughty Secretary
* Live Naughty Student
* Live Naughty Teacher
* Live Party Girl
* Milf Sugar Babes Classic
* My Dad's Hot Girlfriend
* My Daughter's Hot Friend
* My First Sex Teacher
* My Friend's Hot Girl
* My Friend's Hot Mom
* My Girl Loves Anal
* My Girlfriend's Busty Friend
* My Naughty Latin Maid
* My Naughty Massage
* My Sister's Hot Friend
* My Wife Is My Pornstar
* My Wife's Hot Friend
* Naughty America
* Naughty Athletics
* Naughty Bookworms
* Naughty Country Girls
* Naughty Flipside
* Naughty Office
* Naughty Rich Girls
Naughty Weddings
* Neighbor Affair
* Open Family
* Perfect Fucking Strangers Classic
* Seduced By A Cougar
* Sleazy Stepdad
* Slut Step Mom
* Slut Step Sister
* Socal Coeds
* Teens Love Cream
* The Passenger
* Tonight's Girlfriend
* Watch Your Mom
* Watch Your Wife
* Wives on Vacation
* **Perv City**
* Anal Overdose
* Banging Beauties
* Chocolate BJs
* Oral Overdose
* Up Her Asshole
* **Private**
* Anal Introductions
* Blacks on Sluts
* I Confess Files
* Mission: Ass Possible
* Private Fetish
* Private MILFs
* Private Stars
* Russian Fake Agent
* Russian Teen Ass
* Sex on the Beach
* Tight and Teen
* **Reality Kings**\*
* 40 Inch Plus\*
* 8th Street Latinas
* Bad Tow Truck\*
* Big Naturals
* Big Tits Boss\*
* Bikini Crashers\*
* CFNM Secret\*
* Captain Stabbin\*
* Cum Fiesta
* Cum Girls\*
* Dangerous Dongs\*
* Euro Sex Parties\*
* Extreme Asses\*
* Extreme Naturals\*
* First Time Auditions\*
* Flower Tucci\*
* Girls of Naked\*
* HD Love\*
* Happy Tugs
* Hot Bush\*
* In the VIP\*
* Mike in Brazil\*
* Mike's Apartment\*
* Milf Hunter
* Milf Next Door\*
* Moms Bang Teens
* Moms Lick Teens
* Money Talks
* Monster Curves\*
* No Faces\*
* Pure 18\*
* RK Prime\*
* Real Orgasms\*
* Round and Brown
* Saturday Night Latinas\*
* See My Wife\*
* Sneaky Sex
* Street BlowJobs\*
* Team Squirt\*
* Teens Love Huge Cocks
* Top Shelf Pussy\*
* Tranny Surprise
* VIP Crew\*
* We Live Together
* Wives in Pantyhose\*
* **Vixen**
* Blacked
* Blacked Raw
* Tushy
* Tushy Raw
* Vixen
* **XEmpire**
* DarkX
* EroticaX
* HardX
* LesbianX
1121 channels on 83 networks, continuously expanding!
## Notes
* **Naughty America**: Scene titles are not shown in NA's 'latest' overviews. They are derived from a hyperlink and will be stripped of any punctuation and capitalization. Individual scenes fetched by URL with `--scene` are not affected.
* **Reality Kings**: Only RK sites without a `*` can be fetched individually by URL using `--scene`, as most RK sites do not show comprehensive details on their scene pages.
* 21Naturals
* 21Sextreme
* 21Sextury
* Adult Time
* Amateur Allure
* Amateur Euro
* American Pornstar
* Assylum
* Aziani (Gangbang Creampies)
* Babes
* Bang!
* Bang Bros
* Blowpass
* Brazzers
* Burning Angel
* Cherry Pimps
* CzechAV
* DDF Network / Porn World
* Digital Playground
* Dogfart Network
* Dorcel
* Elegant Angel
* Evil Angel
* Fake Hub
* Fame Digital
* Fantasy Massage
* FCUK (Exploited College Girls)
* First Anal Quest
* ForBondage
* Full Porn Network (Analized, James Deen)
* Gaywire
* Girlsway
* Hitzefrei
* Hookup Hotshot
* Hush Pass
* Hussie Pass
* In The Crack
* Insex
* Interracial Pass
* JayRock Productions
* Jesse Loads Monster Facials
* Jules Jordan
* Karups
* Kelly Madison Media (Teen Fidelity)
* Killergram
* Kink
* LegalPorno
* LetsDoeIt
* Little Caprice Dreams
* Mamacitaz
* Men
* Metro HD
* Mike Adriano
* Mile High Media
* MOFOS
* Naughty America
* New Sensations
* Nubiles
* Pascal's Sub Sluts
* Perfect Gonzo
* Perv City
* Pimp.XXX
* Pinky XXX
* Porn Pros
* PornCZ
* Private
* Pure Taboo
* Reality Kings
* SCORE
* Sexy Hub
* Team Skeet
* Teen Core Club
* TransBella
* Twistys
* VIP Sex Vault
* Vivid
* Vixen
* VogoV
* Whale Member (Holed, POVD)
* Wicked
* XEmpire
* ZTOD

File diff suppressed because it is too large Load Diff

View File

@@ -1,48 +1,770 @@
<template>
<div class="actors">
<Actor
v-for="actor in actors"
:key="`actor-${actor.id}`"
:actor="actor"
/>
</div>
<div
ref="content"
class="actors"
>
<nav
ref="filters"
class="filters"
>
<div class="filters-row">
<ul class="genders nolist">
<li class="gender">
<router-link
:to="{ name: 'actors', params: { gender: 'all', pageNumber: 1 }, query: $route.query }"
:class="{ selected: gender === 'all' }"
class="gender-link all"
>all</router-link>
</li>
<li class="gender">
<router-link
:to="{ name: 'actors', params: { gender: 'female', pageNumber: 1 }, query: $route.query }"
: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', pageNumber: 1 }, query: $route.query }"
: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', pageNumber: 1 }, query: $route.query }"
: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', pageNumber: 1 }, query: $route.query }"
:class="{ selected: gender === 'other' }"
class="gender-link other"
replace
><Icon icon="question5" /></router-link>
</li>
</ul>
<ul class="filters-attributes 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>
<li>
<Tooltip class="filter">
<span
:class="{ enabled: country }"
class="filter-trigger"
><img
v-if="$route.query.c"
:src="`/img/flags/${$route.query.c.toLowerCase()}.svg`"
class="flag"
><Icon
v-else
icon="earth2"
/>Country</span>
<template v-slot:tooltip>
<input
v-model="countryQuery"
placeholder="Search"
class="input"
>
<Countries
v-if="!countryQuery"
:countries="topCountries"
:selected-country="country"
:update-value="updateValue"
/>
<Countries
:countries="filteredCountries"
:selected-country="country"
:update-value="updateValue"
/>
</template>
</Tooltip>
</li>
</ul>
</div>
<SearchBar :placeholder="`Search ${totalCount} actors`" />
</nav>
<div
ref="tiles"
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 Actor from '../tile/actor.vue';
import dayjs from 'dayjs';
import Actor from './tile.vue';
import Gender from './gender.vue';
import Checkbox from '../form/checkbox.vue';
import Countries from './countries.vue';
import RangeFilter from './filter-range.vue';
import SearchBar from '../search/bar.vue';
import Pagination from '../pagination/pagination.vue';
const toggleValues = [true, null, false];
const boobSizes = 'ABCDEFGHIJKZ'.split('');
const topCountries = ['AU', 'BR', 'CZ', 'DE', 'JP', 'RU', 'GB', 'US'];
function updateFilters() {
this.$router.push({
name: 'actors',
params: {
pageNumber: 1,
gender: this.gender,
},
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,
c: this.country ? this.country : undefined,
age: this.ageRequired ? this.age.join(',') : undefined,
dob: this.dobRequired ? this.dob : undefined,
query: this.$route.query.query,
},
});
}
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, countries, totalCount } = await this.$store.dispatch('fetchActors', {
limit: this.limit,
pageNumber: Number(this.$route.params.pageNumber) || 1,
query: this.$route.query.query,
gender: curatedGender === 'other' ? null : curatedGender,
age: this.ageRequired && this.age,
dob: this.dobRequired && this.dob,
boobSize: this.boobSizeRequired && this.boobSize,
country: this.country,
naturalBoobs: toggleValues[this.naturalBoobs] ?? null,
height: this.heightRequired && this.height,
weight: this.weightRequired && this.weight,
});
const countriesByAlpha2 = countries.reduce((acc, country) => ({ ...acc, [country.alpha2]: country }), {});
this.actors = actors;
this.totalCount = totalCount;
this.countries = countries;
this.topCountries = [...(this.country && !topCountries.includes(this.country) ? [this.country] : []), ...topCountries].map(alpha2 => countriesByAlpha2[alpha2]);
if (scroll) {
this.$refs.tiles?.scrollIntoView();
}
}
function filteredCountries() {
const countryQueryExpression = new RegExp(this.countryQuery, 'i');
return this.countryQuery?.length > 0
? this.countries.filter(country => countryQueryExpression.test(country.name) || countryQueryExpression.test(country.alpha2))
: this.countries;
}
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;
await this.fetchActors(scroll);
}
async function mounted() {
this.pageTitle = 'Actors';
this.actors = await this.$store.dispatch('fetchActors', { limit: 1000 });
this.pageTitle = 'Actors';
await this.fetchActors();
}
export default {
components: {
Actor,
},
data() {
return {
actors: [],
pageTitle: null,
};
},
mounted,
components: {
Actor,
Checkbox,
Countries,
Gender,
RangeFilter,
SearchBar,
Pagination,
},
data() {
return {
actors: [],
countries: [],
topCountries: [],
countryQuery: null,
pageTitle: null,
totalCount: 0,
limit: 50,
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,
country: this.$route.query.c || null,
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: {
gender,
filteredCountries,
},
watch: {
$route: route,
},
mounted,
methods: {
fetchActors,
updateFilters,
updateValue,
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
<style lang="scss">
.gender-link {
&.selected .gender .icon {
fill: var(--text-light);
filter: none;
}
.actors {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(10rem, .5fr));
grid-gap: 0 .5rem;
padding: 1rem;
}
&:hover:not(.selected) {
.gender .icon {
fill: var(--text-light);
}
@media(max-width: $breakpoint) {
.actors {
grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
.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;
flex-grow: 1;
overflow-y: auto;
}
.tiles {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
grid-template-rows: min-content;
grid-gap: .5rem;
padding: 1rem;
flex-grow: 1;
}
.search {
width: 0;
justify-content: flex-end;
flex-grow: 1;
box-sizing: border-box;
padding: 0 1rem;
}
.filters,
.filters-row {
display: flex;
justify-content: flex-end;
align-items: center;
}
.filters {
margin: 1rem 0 .5rem 0;
}
.filters-row,
.filter {
padding: 0 1rem;
}
.genders {
display: flex;
flex-shrink: 0;
padding: 0 .5rem 0 0;
}
.gender {
display: inline-block;
}
.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,
.flag {
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-mega) {
.filters {
flex-direction: column-reverse;
}
::v-deep(.search) {
width: 100%;
justify-content: center;
margin: 0 0 1rem 0;
}
}
@media(max-width: $breakpoint-kilo) {
.filters {
margin: 1rem 0 0 0;
}
.filters-row {
flex-direction: column;
.filter {
padding: 0 1rem 1rem 1rem;
}
}
.filters-attributes {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.genders {
padding: 0;
margin: 0 0 1.5rem 0;
}
.tiles {
padding: .5rem 1rem 1rem 1rem;
}
}
@media(max-width: $breakpoint-micro) {
.tiles {
grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
}
}
</style>

View File

@@ -0,0 +1,88 @@
<template>
<ul class="countries nolist">
<li
v-for="country in countries"
:key="country.alpha2"
:title="country.name"
:class="{ selected: selectedCountry === country.alpha2 }"
class="country"
@click="updateValue('country', country.alpha2, true)"
>
<img
:src="`/img/flags/${country.alpha2.toLowerCase()}.svg`"
class="flag"
>
<span class="country-name">{{ country.alias || country.name }}</span>
<Icon
v-if="selectedCountry === country.alpha2"
icon="cross2"
@click.native.stop="updateValue('country', null, true)"
/>
</li>
</ul>
</template>
<script>
export default {
props: {
countries: {
type: Array,
default: () => [],
},
selectedCountry: {
type: String,
default: null,
},
updateValue: {
type: Function,
default: null,
},
},
};
</script>
<style lang="scss" scoped>
.countries:not(:last-child) {
border-bottom: solid 1px var(--shadow-hint);
}
.country {
width: 15rem;
display: flex;
align-items: center;
overflow: hidden;
.flag {
width: 1.25rem;
padding: .25rem .5rem;
}
.icon {
padding: .25rem .5rem;;
fill: var(--shadow);
&:hover {
fill: var(--shadow-strong);
}
}
&:hover {
background: var(--shadow-hint);
cursor: pointer;
}
&.selected {
font-weight: bold;
}
}
.country-name {
flex-grow: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding: .25rem .5rem;
}
</style>

View File

@@ -0,0 +1,215 @@
<template>
<div class="filter-section">
<label class="filter-label noselect">
<span class="label">
<Checkbox
:checked="!disabled"
class="checkbox"
@change="(checked) => $emit('enable', checked)"
/>{{ label }}
</span>
<span
v-if="!disabled"
class="label-values"
>{{ value[0] }} - {{ value[1] }}<template v-if="unit">&nbsp;{{ unit }}</template></span>
</label>
<span class="filter-split">
<Range
:min="min"
:max="max"
:value="value"
:values="values"
:disabled="disabled"
:allow-enable="allowEnable"
@enable="$emit('enable', true)"
@input="(range) => $emit('input', range)"
@change="(range) => $emit('change', range)"
>
<template v-slot:start><slot name="start" /></template>
<template v-slot:end><slot name="end" /></template>
</Range>
</span>
</div>
</template>
<script>
import Checkbox from '../form/checkbox.vue';
import Range from '../form/range.vue';
export default {
components: {
Checkbox,
Range,
},
props: {
label: {
type: String,
default: null,
},
value: {
type: Array,
default: () => [0, 10],
},
values: {
type: Array,
default: null,
},
min: {
type: Number,
default: 0,
},
max: {
type: Number,
default: 10,
},
unit: {
type: String,
default: null,
},
disabled: {
type: Boolean,
default: false,
},
allowEnable: {
type: Boolean,
default: true,
},
},
emits: ['change', 'input', 'enable'],
};
</script>
<style lang="scss" scoped>
@import 'breakpoints';
.filter-section {
width: 15rem;
max-width: 100%;
border-bottom: solid 1px var(--shadow-hint);
}
.filter-label {
display: flex;
justify-content: space-between;
padding: .75rem .5rem .5rem .5rem;
color: var(--shadow);
font-weight: bold;
font-size: .9rem;
.label {
display: inline-flex;
align-items: center;
text-transform: capitalize;
}
.checkbox {
margin: 0 .75rem 0 0;
}
.icon {
margin: 0 .5rem 0 0;
}
}
.label-values {
font-weight: normal;
}
.filter-split {
display: flex;
align-items: center;
}
.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);
}
}
}
}
</style>

View File

@@ -0,0 +1,41 @@
<template>
<span
v-if="gender"
class="gender"
:class="{ [gender]: true }"
><Icon :icon="gender" /></span>
</template>
<script>
export default {
props: {
gender: {
type: String,
default: null,
},
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
.gender {
&.female .icon {
fill: var(--female);
filter: drop-shadow(0 0 1px var(--darken));
}
&.male .icon {
fill: var(--male);
filter: drop-shadow(0 0 1px var(--darken));
}
&.transsexual .icon {
fill: var(--text-light);
filter: drop-shadow(1px 0 0 var(--female)) drop-shadow(-1px 0 0 var(--female)) drop-shadow(0 1px 0 var(--female)) drop-shadow(0 -1px 0 var(--female))
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 var(--darken))
}
}
</style>

View File

@@ -1,45 +1,73 @@
<template>
<div
class="photos"
:class="{ wide: actor.photos.length > 2 }"
>
<a
v-if="actor.avatar"
:href="`/media/${actor.avatar.path}`"
target="_blank"
rel="noopener noreferrer"
class="avatar-link photo-link"
>
<img
:src="`/media/${actor.avatar.thumbnail}`"
class="avatar photo"
>
</a>
<div
class="photos"
:class="{
avatar: !!actor.avatar,
empty: actor.photos.length === 0,
}"
>
<a
v-if="actor.avatar"
:href="getPath(actor.avatar)"
target="_blank"
rel="noopener noreferrer"
class="avatar-link photo-link"
>
<img
:src="getPath(actor.avatar, 'thumbnail')"
:style="{ 'background-image': getBgPath(actor.avatar, 'lazy') }"
:title="actor.avatar.credit && `© ${actor.avatar.credit}`"
:width="actor.avatar.thumbnailWidth"
:height="actor.avatar.thumbnailHeight"
loading="lazy"
class="avatar photo"
@load="$emit('load', $event)"
>
</a>
<a
v-for="photo in actor.photos"
:key="`photo-${photo.id}`"
:href="`/media/${photo.path}`"
target="_blank"
rel="noopener noreferrer"
class="photo-link"
>
<img
:src="`/media/${photo.thumbnail}`"
class="photo"
>
</a>
</div>
<a
v-for="photo in photos"
:key="`photo-${photo.id}`"
:href="getPath(photo)"
target="_blank"
rel="noopener noreferrer"
class="photo-link"
>
<img
:src="getPath(photo, 'thumbnail')"
:style="{ 'background-image': getBgPath(photo, 'lazy') }"
:title="`© ${photo.credit || photo.entity.name}`"
:width="photo.thumbnailWidth"
:height="photo.thumbnailHeight"
loading="lazy"
class="photo"
@load="$emit('load', $event)"
>
</a>
</div>
</template>
<script>
function photos() {
return this.actor.photos.filter(photo => !photo.entropy || photo.entropy > 5.5);
}
function sfw() {
return this.$store.state.ui.sfw;
}
export default {
props: {
actor: {
type: Object,
default: null,
},
},
props: {
actor: {
type: Object,
default: null,
},
},
emits: ['load'],
computed: {
sfw,
photos,
},
};
</script>
@@ -47,59 +75,70 @@ export default {
@import 'theme';
.photos {
display: inline-grid;
grid-template-columns: repeat(auto-fit, 12rem);
grid-gap: .5rem;
display: flex;
box-sizing: border-box;
padding: .5rem 1rem;
font-size: 0;
&.expanded {
flex-wrap: wrap;
justify-content: center;
margin: 0;
.photo-link {
margin: 0 .5rem .5rem 0;
}
.photo {
height: 18rem;
}
}
&.empty {
display: none;
}
.avatar-link {
display: none;
}
&.compact {
.photo {
width: auto;
}
}
}
.photo-link {
padding: 0 .5rem 0 0;
&:last-child {
padding: 0 1rem 0 0;
}
}
.photo {
width: 100%;
height: 100%;
object-fit: cover;
box-shadow: 0 0 3px $shadow-weak;
}
@media(min-width: $breakpoint3) {
.photos.wide {
max-width: 30vw;
}
height: 15rem;
width: auto;
box-shadow: 0 0 3px var(--darken-weak);
object-fit: cover;
background-position: center;
background-size: cover;
}
@media(max-width: $breakpoint3) {
.photos {
width: 100%;
max-width: 100%;
display: flex;
overflow-x: scroll;
scrollbar-width: none;
.photos {
&.empty.avatar {
display: flex;
}
.avatar-link {
display: inline-block;
}
.avatar-link {
display: inline-block;
}
&::-webkit-scrollbar {
display: none;
}
&.expanded {
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
}
}
}
.photo-link {
height: 15rem;
flex-shrink: 0;
margin: 0 .5rem 0 0;
@media(max-width: $breakpoint0) {
.photos.expanded {
grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
}
}
</style>

View File

@@ -0,0 +1,86 @@
<template>
<router-link
:to="`/actor/${actor.id}/${actor.slug}`"
class="actor nolink"
>
<div class="avatar">
<img
v-if="actor.avatar"
:src="getPath(actor.avatar, 'lazy')"
class="avatar-image"
>
<Icon
v-else-if="actor.gender"
:icon="actor.gender"
class="avatar-fallback"
/>
</div>
<span class="name">{{ actor.name }}</span>
</router-link>
</template>
<script>
async function unstashActor(actorId, stashId) {
await this.$store.dispatch('unstashActor', { actorId, stashId });
}
export default {
props: {
actor: {
type: Object,
default: null,
},
stash: {
type: Object,
default: null,
},
},
methods: {
unstashActor,
},
};
</script>
<style lang="scss" scoped>
.actor {
height: 2.5rem;
display: inline-flex;
align-items: center;
border: solid 1px var(--shadow-hint);
&:hover {
border: solid 1px var(--primary);
}
}
.avatar {
display: inline-flex;
align-items: center;
justify-content: center;
width: 2rem;
height: 100%;
border-right: solid 1px var(--shadow-hint);
}
.avatar-image {
width: 100%;
height: 100%;
object-fit: cover;
object-position: 50% 0;
}
.avatar-fallback {
fill: var(--shadow-weak);
}
.name {
display: inline-flex;
align-items: center;
height: 100%;
padding: 0 .5rem;
font-size: .8rem;
font-weight: bold;
}
</style>

View File

@@ -1,38 +1,38 @@
<template>
<div
v-if="actor"
class="social"
>
<a
v-for="social in actor.social"
:key="`social-${social.id}`"
v-tooltip.bottom="social.url"
:href="social.url"
target="_blank"
rel="noopener noreferrer"
class="social-link"
>
<Icon
v-if="social.platform"
:icon="social.platform"
/>
<div
v-if="actor"
class="social"
>
<a
v-for="social in actor.social"
:key="`social-${social.id}`"
v-tooltip.bottom="social.url"
:href="social.url"
target="_blank"
rel="noopener noreferrer"
class="social-link"
>
<Icon
v-if="social.platform"
:icon="social.platform"
/>
<Icon
v-else
icon="link"
/>
</a>
</div>
<Icon
v-else
icon="link"
/>
</a>
</div>
</template>
<script>
export default {
props: {
actor: {
type: Object,
default: null,
},
},
props: {
actor: {
type: Object,
default: null,
},
},
};
</script>
@@ -48,14 +48,14 @@ export default {
padding: 0 0 0 1rem;
.icon {
color: $highlight;
fill: $highlight;
color: var(--highlight);
fill: var(--highlight);
width: 1.5rem;
height: 1.5rem;
}
&:hover .icon {
fill: $primary;
fill: var(--primary);
}
}
</style>

View File

@@ -0,0 +1,386 @@
<template>
<div
v-if="actor"
class="actor"
>
<RouterLink
:to="{ name: 'actor', params: { actorId: actor.id, actorSlug: actor.slug } }"
class="link"
>
<span
class="handle"
>
<span
v-tooltip.top="actor.name"
class="name"
>{{ actor.name }}</span>
<RouterLink
v-if="actor.entity"
v-tooltip="actor.entity.name"
:to="{ name: actor.entity.type, params: { entitySlug: actor.entity.slug, range: 'new', pageNumber: 1 } }"
class="favicon"
>
<img
:src="`/img/logos/${actor.entity.slug}/favicon_dark.png`"
class="favicon-icon"
>
</RouterLink>
<Icon
v-if="alias"
v-tooltip="`Alias of ${alias.name}`"
icon="users3"
class="favicon alias"
/>
<Icon
v-if="actor.dateOfDeath"
v-tooltip="`Died ${formatDate(actor.dateOfDeath, 'MMMM D, YYYY')}`"
icon="tombstone"
class="favicon died"
/>
</span>
<div class="avatar-container">
<img
v-if="actor.avatar"
:src="getPath(actor.avatar, 'thumbnail')"
:style="{ 'background-image': getBgPath(actor.avatar, 'lazy') }"
loading="lazy"
class="avatar"
>
<span
v-else
class="avatar"
><img
:src="`/img/avatar_${actor.gender || 'female'}.svg`"
class="avatar-fallback"
></span>
<Icon
v-show="(!stash || stash.primary) && favorited"
icon="heart7"
class="stash stashed"
@click.prevent.native="unstashActor"
/>
<Icon
v-show="(!stash || stash.primary) && favorited === false"
icon="heart8"
class="stash unstashed"
@click.prevent.native="stashActor"
/>
<Icon
v-show="stash && !stash.primary"
icon="cross2"
class="stash unstash"
@click.prevent.native="unstashActor"
/>
<span
class="details"
>
<span class="gender-age">
<Gender :gender="actor.gender" />
<span
v-if="actor.ageAtDeath"
v-tooltip="`Born ${formatDate(actor.dateOfBirth, 'MMMM D, YYYY')}<br>Died ${formatDate(actor.dateOfDeath, 'MMMM D, YYYY')}`"
class="age-death"
>{{ actor.ageAtDeath }}</span>
<span
v-else-if="actor.ageFromBirth"
v-tooltip="`Born on ${formatDate(actor.dateOfBirth, 'MMMM D, YYYY')}`"
class="age-now"
>{{ actor.ageFromBirth }}</span>
<span
v-else-if="actor.age"
v-tooltip="`At least ${actor.age}`"
class="age-now"
>{{ actor.age }}</span>
<span
v-if="actor.ageThen && actor.ageThen < actor.ageFromBirth"
v-tooltip="`${actor.ageThen} years old on release date`"
class="age-then"
>{{ actor.ageThen }}</span>
</span>
<span
v-if="actor.origin"
v-tooltip="`Born in ${actor.origin.country.alias || actor.origin.country.name}`"
class="country"
>
{{ actor.origin.country.alpha2 }}
<img
class="flag"
:src="`/img/flags/${actor.origin.country.alpha2.toLowerCase()}.svg`"
>
</span>
<span
v-else
class="country"
/>
</span>
</div>
</RouterLink>
</div>
</template>
<script>
import Gender from './gender.vue';
async function stashActor() {
this.favorited = true;
try {
await this.$store.dispatch('stashActor', {
actorId: this.actor.id,
stashId: this.$store.getters.favorites.id,
});
this.$emit('stash', true);
} catch (error) {
this.favorited = false;
}
}
async function unstashActor() {
if (!this.stash || this.stash.primary) {
this.favorited = false;
}
try {
await this.$store.dispatch('unstashActor', {
actorId: this.actor.id,
stashId: this.stash?.id || this.$store.getters.favorites.id,
});
this.$emit('stash', false);
} catch (error) {
this.favorited = true;
}
}
function sfw() {
return this.$store.state.ui.sfw;
}
export default {
components: {
Gender,
},
props: {
actor: {
type: Object,
default: null,
},
alias: {
type: Object,
default: null,
},
stash: {
type: Object,
default: null,
},
},
emits: ['stash'],
data() {
return {
favorited: this.actor.isFavorited,
};
},
computed: {
sfw,
},
methods: {
stashActor,
unstashActor,
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
.actor {
width: 100%;
display: inline-block;
position: relative;
box-shadow: 0 0 3px var(--darken-weak);
background: var(--info);
overflow: hidden;
&::before {
content: '';
display: inline-block;
padding-bottom: 150%;
}
&:hover .unstashed,
&:hover .unstash {
fill: var(--lighten);
}
}
.link {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
position: absolute;
top: 0;
left: 0;
color: var(--text-light);
text-decoration: none;
}
.handle {
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
box-shadow: 0 0 3px var(--shadow);
.name {
padding: .25rem .5rem;
font-size: .9rem;
}
.alias {
fill: var(--shadow);
}
}
.favicon {
font-size: 0;
padding: .5rem .25rem;
&:last-child {
padding: .5rem;
}
&.died {
fill: var(--shadow);
}
}
.favicon-icon {
width: 1rem;
height: 1rem;
}
.name {
flex-grow: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.avatar-container {
display: flex;
flex-grow: 1;
position: relative;
overflow: hidden;
background: var(--profile);
}
.avatar {
color: var(--darken-weak);
background: var(--darken-hint);
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
object-fit: cover;
object-position: center 0;
background-size: cover;
background-position: center 0;
}
.avatar-fallback {
max-height: 100%;
max-width: 100%;
opacity: .1;
}
.stash {
width: 1.5rem;
height: 1.5rem;
padding: .25rem .25rem .5rem .5rem;
position: absolute;
top: 0;
right: 0;
fill: var(--lighten-weak);
filter: drop-shadow(0 0 2px var(--darken));
&:hover.unstashed,
&.stashed {
fill: var(--primary);
}
&:hover.unstash {
fill: var(--text-light);
filter: drop-shadow(0 0 2px var(--darken-weak));
}
}
.details {
background: var(--darken);
color: var(--text-light);
width: 100%;
height: 1.75rem;
display: flex;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
padding: .5rem;
position: absolute;
bottom: 0;
font-size: .9rem;
font-weight: bold;
}
.gender-age {
display: flex;
align-items: center;
}
.gender {
margin: .25rem .25rem 0 0;
}
.country {
display: flex;
justify-content: flex-end;
align-items: center;
}
.flag {
height: .75rem;
margin: 0 0 0 .5rem;
}
.age-now,
.age-death {
margin: 0 .25rem 0 0;
}
.age-then {
color: var(--lighten);
}
@media(max-width: $breakpoint) {
.name {
font-size: .9rem;
}
}
</style>

View File

@@ -0,0 +1,295 @@
<template>
<teleport to="body">
<div class="album">
<div class="album-header">
<h3 class="album-title">{{ title }}</h3>
<Icon
icon="cross2"
class="close"
@click.native="$emit('close')"
/>
</div>
<div class="album-body">
<div
v-if="entity || tag"
class="campaign-container"
>
<Campaign
:entity="entity"
:tag="tag"
:min-ratio="3"
/>
</div>
<div
class="album-items"
:class="{ portrait }"
>
<div
v-for="item in albumItems"
:key="item.id"
class="item-container"
>
<a
:href="getPath(item, null, { local })"
class="item-link"
target="_blank"
>
<img
:src="getPath(item, 'thumbnail', { local })"
:style="{ 'background-image': getBgPath(item, 'lazy', { local }) }"
:width="item.thumbnailWidth"
:height="item.thumbnailHeight"
:title="item.title"
loading="lazy"
class="item image"
>
<Logo :photo="item" />
<router-link
v-if="comments && item.title && item.entity"
:to="`/${item.entity.type}/${item.entity.slug}`"
class="item-comment"
>{{ item.title }} for {{ item.entity.name }}</router-link>
<span
v-else-if="comments && item.title"
class="item-comment"
>{{ item.title }}</span>
</a>
</div>
</div>
</div>
</div>
</teleport>
</template>
<script>
import Logo from './logo.vue';
import Campaign from '../campaigns/campaign.vue';
function albumItems() {
return this.items
.filter(Boolean)
.map(item => ({
...item,
title: item.comment || (item.credit && `© ${item.credit}`) || (item.entity && `© ${item.entity.name}`),
}));
}
export default {
components: {
Logo,
Campaign,
},
props: {
items: {
type: Array,
default: () => [],
},
title: {
type: String,
default: null,
},
local: {
type: Boolean,
default: false,
},
portrait: {
type: Boolean,
default: false,
},
comments: {
type: Boolean,
default: true,
},
entity: {
type: Object,
default: null,
},
tag: {
type: Object,
default: null,
},
},
computed: {
albumItems,
},
emits: ['close'],
};
</script>
<style lang="scss" scoped>
@import 'breakpoints';
.album {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
position: fixed;
top: 0;
left: 0;
background: var(--darken-extreme);
z-index: 10;
}
.album-header {
display: flex;
justify-content: space-between;
flex-shrink: 0;
}
.album-title {
display: block;
flex-grow: 1;
align-items: center;
padding: 1rem;
margin: 0;
color: var(--text-light);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-align: center;
text-transform: capitalize;
}
.close {
width: 1.5rem;
height: 1.5rem;
padding: 1rem;
fill: var(--lighten);
&:hover {
cursor: pointer;
fill: var(--text-light);
}
}
.album-body {
display: flex;
flex-grow: 1;
flex-direction: column;
justify-content: center;
overflow-y: auto;
}
.campaign-container {
flex-shrink: 0;
text-align: center;
}
.album-items {
height: 0;
display: grid;
flex-grow: 1;
flex-shrink: 0;
align-items: center;
justify-content: center;
grid-template-columns: repeat(auto-fit, minmax(25rem, 1fr));
grid-gap: 0 1rem;
padding: 1rem;
margin: auto 0;
&.portrait {
grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
}
}
.item-container {
display: flex;
align-items: center;
justify-content: center;
}
.item-link {
position: relative;
margin: 0 0 .5rem 0;
overflow: hidden;
&:hover {
.item-comment {
transform: translateY(0);
}
::v-deep(.album-logo) {
opacity: 0;
}
}
}
.item {
max-width: 100%;
max-height: 100%;
height: auto;
background-size: cover;
background-position: center;
}
.item-comment {
width: 100%;
position: absolute;
bottom: 0;
left: 0;
box-sizing: border-box;
padding: .5rem;
color: var(--text-light);
background: var(--darken);
font-size: .9rem;
text-shadow: 0 0 3px var(--darken);
text-decoration: none;
white-space: normal;
line-height: 1.25;
transform: translateY(100%);
transition: transform .25s ease;
}
@media(max-width: $breakpoint-giga) {
.album-items {
grid-template-columns: repeat(auto-fill, minmax(22.5rem, 1fr));
.portrait {
grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
}
}
}
@media(max-width: $breakpoint-mega) {
.album-items.portrait {
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
}
}
@media(max-width: $breakpoint-kilo) {
.album-items {
grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
grid-gap: 0 .5rem;
padding: .5rem;
}
.item-link {
margin: 0 0 .25rem 0;
}
}
@media(max-width: $breakpoint) {
.album-items {
grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
}
}
@media(max-width: $breakpoint-micro) {
.album-items {
grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
}
}
@media(max-width: $breakpoint-pico) {
.album-items {
grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr));
}
}
</style>

View File

@@ -0,0 +1,66 @@
<template>
<router-link
v-if="photo.entity"
v-tooltip="photo.entity.name"
:to="`/${photo.entity.type}/${photo.entity.slug}`"
>
<img
v-if="favicon && photo.entity.type !== 'network' && !photo.entity.independent && photo.entity.parent"
:src="`/img/logos/${photo.entity.parent.slug}/favicon.png`"
class="album-logo favicon"
>
<img
v-else-if="favicon"
:src="`/img/logos/${photo.entity.slug}/favicon.png`"
class="album-logo favicon"
>
<img
v-else-if="photo.entity.type !== 'network' && !photo.entity.independent && photo.entity.parent"
:src="`/img/logos/${photo.entity.parent.slug}/${photo.entity.slug}.png`"
class="album-logo"
>
<img
v-else
:src="`/img/logos/${photo.entity.slug}/network.png`"
class="album-logo"
>
</router-link>
</template>
<script>
module.exports = {
props: {
photo: {
type: Object,
default: null,
},
favicon: {
type: Boolean,
default: false,
},
},
};
</script>
<style lang="scss" scoped>
.album-logo {
max-height: .75rem;
max-width: 5rem;
position: absolute;
right: 0;
bottom: 0;
padding: .5rem;
transition: transform .25s ease, opacity .25s ease;
filter: drop-shadow(0 0 2px var(--shadow-weak));
}
@media(max-width: $breakpoint-small) {
.album-logo:not(.favicon) {
max-height: .5rem;
max-width: 3.5rem;
}
}
</style>

View File

@@ -0,0 +1,441 @@
<template>
<Dialog
title="Add alert"
@close="$emit('close')"
>
<form
class="dialog-body"
@submit.prevent="addAlert"
>
<div class="dialog-section">
<h3 class="dialog-heading">
When<span class="dialog-description">All to appear in the same scene</span>
</h3>
<div class="alert-section">
<h4
v-if="actors.length > 1"
class="alert-heading"
>Actors</h4>
<h4
v-else
class="alert-heading"
>Actor</h4>
<ul class="actors nolist">
<li
v-for="actor in actors"
:key="`actor-${actor.id}`"
class="actor"
>
<ActorPreview :actor="actor" />
<Icon
icon="cross3"
class="remove"
@click.native="removeActor(actor)"
/>
</li>
<Tooltip>
<li class="actor placeholder"><template v-if="actors.length === 0">Any actor</template>
<Icon
icon="plus3"
class="add"
/>
</li>
<template #tooltip>
<Search
content="actors"
@select="actor => addActor(actor)"
/>
</template>
</Tooltip>
</ul>
</div>
<div class="alert-section">
<h4
v-if="actors.length > 1"
class="alert-heading"
>Do</h4>
<h4
v-else
class="alert-heading"
>Does</h4>
<ul class="tags nolist">
<li
v-for="tag in tags"
:key="`tag-${tag.id}`"
class="tag"
>{{ tag.name }}
<Icon
icon="cross3"
class="remove"
@click.native="removeTag(tag)"
/>
</li>
<Tooltip>
<li class="tag placeholder"><template v-if="tags.length === 0">Any type of scene</template>
<Icon
icon="plus3"
class="add"
/>
</li>
<template #tooltip>
<Search
content="tags"
:defaults="['anal', 'blowbang', 'mfm', 'dp', 'gangbang', 'airtight']"
@select="tag => addTag(tag)"
/>
</template>
</Tooltip>
</ul>
</div>
<div class="alert-section">
<h4 class="alert-heading">For</h4>
<div class="entities">
<div
v-if="entity"
class="entity"
>
<Entity :entity="entity" />
<Icon
icon="cross3"
class="remove"
@click.native="removeEntity(entity)"
/>
</div>
<Tooltip v-if="!entity">
<div class="entity placeholder">
Any channel
<Icon
icon="plus3"
class="add"
/>
</div>
<template #tooltip>
<Search
label="Search channels"
content="entities"
@select="entity => addEntity(entity)"
/>
</template>
</Tooltip>
</div>
</div>
</div>
<div class="dialog-section">
<h3 class="dialog-heading">Then</h3>
<label class="alert-label">
<Checkbox
:checked="notify"
@change="checked => notify = checked"
/>Notify me in traxxx
</label>
<!--
<label class="alert-label">
<Checkbox
:checked="email"
@change="checked => email = checked"
/>Send me an e-mail
</label>
-->
<div class="stashes-container">
<ul class="stashes nolist">
<li
v-for="stash in stashes"
:key="`stash-${stash.id}`"
class="stash"
>{{ stash.name }}
<Icon
icon="cross3"
class="remove"
@click.native="removeStash(stash)"
/>
</li>
<Tooltip>
<li class="stash placeholder">
Add to stash
<Icon
icon="plus3"
class="add"
/>
</li>
<template #tooltip>
<Search
content="stashes"
@select="stash => addStash(stash)"
/>
</template>
</Tooltip>
</ul>
</div>
</div>
<div class="dialog-actions right">
<button
:disabled="actors.length === 0 && tags.length === 0 && !entity"
type="submit"
class="button button-primary"
>Add alert</button>
</div>
</form>
</Dialog>
</template>
<script>
import ActorPreview from '../actors/preview.vue';
import Entity from '../entities/tile.vue';
import Checkbox from '../form/checkbox.vue';
import Search from './search.vue';
async function addAlert() {
await this.$store.dispatch('addAlert', {
actors: this.actors.map((actor) => actor.id),
tags: this.tags.map((tag) => tag.id),
entity: this.entity?.id,
notify: this.notify,
email: this.email,
stashes: this.stashes.map((stash) => stash.id),
});
this.$emit('close', true);
}
function addActor(actor) {
if (!this.actors.some((selectedActor) => selectedActor.id === actor.id)) {
this.actors = this.actors.concat(actor);
}
this.events.emit('blur');
}
function addEntity(entity) {
this.entity = entity;
this.events.emit('blur');
}
function addTag(tag) {
if (!this.tags.some((selectedTag) => selectedTag.id === tag.id)) {
this.tags = this.tags.concat(tag);
}
this.events.emit('blur');
}
function removeActor(actor) {
this.actors = this.actors.filter((listedActor) => listedActor.id !== actor.id);
}
function removeEntity() {
this.entity = null;
}
function removeTag(tag) {
this.tags = this.tags.filter((listedTag) => listedTag.id !== tag.id);
}
function addStash(stash) {
if (!this.stashes.some((selectedStash) => selectedStash.id === stash.id)) {
this.stashes = this.stashes.concat(stash);
}
this.events.emit('blur');
}
function removeStash(stash) {
this.stashes = this.stashes.filter((listedStash) => listedStash.id !== stash.id);
}
export default {
components: {
ActorPreview,
Checkbox,
Entity,
Search,
},
emits: ['close'],
data() {
return {
actors: [],
tags: [],
entity: null,
notify: true,
email: false,
stashes: [],
availableStashes: this.$store.state.auth.user.stashes,
};
},
methods: {
addActor,
addAlert,
addEntity,
addTag,
addStash,
removeActor,
removeEntity,
removeTag,
removeStash,
},
};
</script>
<style lang="scss" scoped>
.dialog-section {
width: 30rem;
max-width: 100%;
&:first-child {
border-bottom: solid 1px var(--shadow-hint);
margin: 0 0 1rem 0;
}
}
.dialog-heading {
display: flex;
justify-content: space-between;
align-items: center;
margin: 0 0 .25rem 0;
color: var(--primary);
}
.dialog-description {
color: var(--shadow);
font-size: .9rem;
font-weight: normal;
}
.alert-heading {
margin: .75rem 0 .25rem 0;
}
.actors,
.entities,
.tags {
display: flex;
align-items: center;
flex-wrap: wrap;
font-size: 0;
}
.actors > .actor,
.entity,
.tag,
.stash {
position: relative;
font-size: 1rem;
margin: 0 .5rem .5rem 0;
}
.entity .tile {
width: 10rem;
height: 2.5rem;
}
.tag:not(.placeholder),
.stash:not(.placeholder) {
padding: .5rem .75rem;
border: solid 1px var(--shadow-hint);
margin: 0 .75rem 0 0;
font-size: .9rem;
font-weight: bold;
}
.stashes {
margin: 0 0 0 .25rem;
color: var(--text);
}
.remove {
width: 1rem;
height: 1rem;
position: absolute;
top: -.35rem;
right: -.35rem;
z-index: 1;
border: solid 1px var(--darken-hint);
border-radius: 50%;
background: var(--background);
fill: var(--shadow-weak);
box-shadow: 0 0 1px var(--shadow);
&:hover {
fill: var(--text-light);
background: var(--primary);
border: solid 1px var(--primary);
cursor: pointer;
}
}
.placeholder {
display: inline-flex;
align-items: center;
color: var(--shadow-strong);
padding: .75rem 0;
margin: 0 0 .5rem 0;
font-size: 1rem;
.add {
fill: var(--shadow);
margin: 0 0 0 .5rem;
}
&:hover {
color: var(--primary);
cursor: pointer;
.add {
fill: var(--primary);
}
}
}
.alert-label {
display: flex;
align-items: center;
padding: .5rem 0;
margin: 0 0 .25rem 0;
cursor: pointer;
}
.stashes-heading {
display: flex;
align-items: center;
margin: 0 0 .5rem 0;
font-size: 1rem;
.alert-label {
display: inline-block;
margin: 0;
}
}
.tooltip-container {
display: inline-block;
}
.check-container {
display: inline-block;
margin: 0 .5rem 0 0;
}
</style>

View File

@@ -0,0 +1,41 @@
<template>
<Dialog
title="Remove alert"
@close="$emit('close', false)"
>
<form
class="dialog-body"
@submit.prevent="removeAlert"
>
Are you sure you want to remove alert?
<div class="dialog-actions right">
<button
type="submit"
class="button button-primary"
>Remove</button>
</div>
</form>
</Dialog>
</template>
<script>
async function removeAlert() {
await this.$store.dispatch('removeAlert', this.alert.id);
this.$emit('close', true);
}
export default {
props: {
alert: {
type: Object,
default: null,
},
},
emits: ['close'],
methods: {
removeAlert,
},
};
</script>

View File

@@ -0,0 +1,147 @@
<template>
<div>
<input
ref="input"
v-model="query"
:placeholder="label || `Search ${content}`"
class="input"
@input="search"
>
<ul
v-if="results.length > 0"
class="nolist"
>
<li
v-for="result in results"
:key="`result-${result.id}`"
class="result"
@click="selectResult(result)"
>
<Icon
v-if="result.type === 'network'"
v-tooltip="'Network'"
icon="device_hub"
/>
<Icon
v-if="result.type === 'channel'"
v-tooltip="'Channel'"
icon="tv"
/>
<img
v-if="result.avatar"
:src="getPath(result.avatar)"
class="avatar"
>{{ result.name }}
</li>
</ul>
</div>
</template>
<script>
async function search() {
if (this.content === 'actors') {
const { actors } = await this.$store.dispatch('fetchActors', {
query: this.query,
limit: 10,
});
this.results = actors;
}
if (this.content === 'entities') {
this.results = await this.$store.dispatch('searchEntities', {
query: this.query,
limit: 10,
});
}
if (this.content === 'tags') {
this.results = await this.$store.dispatch('searchTags', {
query: this.query,
minLength: 1,
limit: 10,
});
}
if (this.content === 'stashes') {
this.results = this.$store.state.auth.user.stashes.filter(stash => new RegExp(this.query).test(stash.name));
}
}
function selectResult(item) {
this.query = null;
this.results = [];
this.$emit('select', item);
}
async function mounted() {
this.$refs.input.focus();
if (this.defaults.length > 0 && this.content === 'tags') {
this.results = await this.$store.dispatch('fetchTags', {
slugs: this.defaults,
});
}
if (this.content === 'stashes') {
this.results = this.$store.state.auth.user.stashes;
}
}
export default {
props: {
content: {
type: String,
default: null,
},
defaults: {
type: Array,
default: () => [],
},
label: {
type: String,
default: null,
},
},
data() {
return {
query: null,
results: [],
};
},
emits: ['select'],
mounted,
methods: {
search,
selectResult,
},
};
</script>
<style lang="scss" scoped>
.result {
color: var(--text);
display: flex;
align-items: center;
padding: .5rem;
&:hover {
color: var(--primary);
cursor: pointer;
}
}
.avatar {
height: 2rem;
margin: 0 .5rem 0 0;
}
.icon {
fill: var(--shadow);
margin: -.1rem .75rem 0 0;
}
</style>

View File

@@ -0,0 +1,124 @@
<template>
<form
class="login"
@submit.prevent="login"
>
<div
v-if="error"
class="feedback error"
>{{ error }}</div>
<div
v-if="success"
class="feedback success"
>Login successful, redirecting</div>
<template v-else>
<input
v-model="username"
placeholder="Username or e-mail"
type="text"
class="input"
required
>
<input
v-model="password"
placeholder="Password"
type="password"
class="input"
required
>
<button
type="submit"
class="button button-primary"
>Log in</button>
<RouterLink
v-if="$store.state.auth.signup"
:to="{ name: 'signup', query: { ref: $route.query.ref } }"
class="link link-external signup"
>Sign up</RouterLink>
</template>
</form>
</template>
<script>
async function login() {
this.error = null;
this.success = false;
try {
const user = await this.$store.dispatch('login', {
username: this.username,
password: this.password,
});
this.success = true;
setTimeout(() => {
this.$router.replace(this.$route.query.ref || { name: 'user', params: { username: user.username } });
}, 1000);
} catch (error) {
this.error = error.message;
}
}
function mounted() {
if (!this.$store.state.auth.login) {
this.$router.replace({ name: 'not-found' });
}
}
export default {
data() {
return {
username: null,
password: null,
success: false,
error: null,
};
},
mounted,
methods: {
login,
},
};
</script>
<style lang="scss" scoped>
.login {
width: 20rem;
display: flex;
flex-direction: column;
margin: auto;
}
.input {
margin: 0 0 .5rem 0;
}
.feedback {
padding: 1rem;
text-align: center;
font-weight: bold;
}
.error {
color: var(--error);
}
.success {
color: var(--shadow-strong);
}
.button {
margin: 0 0 .25rem 0;
}
.signup {
padding: .5rem;
text-align: center;
}
</style>

View File

@@ -0,0 +1,134 @@
<template>
<form
class="signup"
@submit.prevent="signup"
>
<div
v-if="error"
class="feedback error"
>{{ error }}</div>
<div
v-if="success"
class="feedback success"
>Signup successful, redirecting</div>
<template v-else>
<input
v-model="username"
placeholder="Username"
type="text"
class="input"
required
>
<input
v-model="email"
placeholder="E-mail"
type="email"
class="input"
required
>
<input
v-model="password"
placeholder="Password"
type="password"
class="input"
required
>
<button
type="submit"
class="button button-primary"
>Sign up</button>
<RouterLink
v-if="$store.state.auth.login"
:to="{ name: 'login', query: { ref: $route.query.ref } }"
class="link link-external login"
>Log in</RouterLink>
</template>
</form>
</template>
<script>
async function signup() {
this.error = null;
this.success = false;
try {
await this.$store.dispatch('signup', {
username: this.username,
email: this.email,
password: this.password,
});
this.success = true;
setTimeout(() => {
this.$router.replace(this.$route.query.ref || { name: 'home' });
}, 1000);
} catch (error) {
this.error = error.message;
}
}
function mounted() {
if (!this.$store.state.auth.signup) {
this.$router.replace({ name: 'not-found' });
}
}
export default {
data() {
return {
username: null,
email: null,
password: null,
success: false,
error: null,
};
},
mounted,
methods: {
signup,
},
};
</script>
<style lang="scss" scoped>
.signup {
width: 20rem;
display: flex;
flex-direction: column;
margin: auto;
}
.input {
margin: 0 0 .5rem 0;
}
.feedback {
padding: 1rem;
text-align: center;
font-weight: bold;
}
.error {
color: var(--error);
}
.success {
color: var(--shadow-strong);
}
.button {
margin: 0 0 .25rem 0;
}
.login {
padding: .5rem;
text-align: center;
}
</style>

View File

@@ -0,0 +1,152 @@
<template>
<a
v-if="campaign"
:href="campaign.url || campaign.affiliate?.url"
target="_blank"
class="campaign"
>
<img
v-if="campaign.banner.entity.type === 'network' || !campaign.banner.entity.parent"
:src="`/img/banners/${campaign.banner.entity.slug}/${campaign.banner.id}.${campaign.banner.type || 'jpg'}`"
:width="campaign.banner.width"
:height="campaign.banner.height"
class="campaign-banner"
>
<img
v-if="campaign.banner.entity.type === 'channel' && campaign.banner.entity.parent?.type === 'network'"
:src="`/img/banners/${campaign.banner.entity.parent.slug}/${campaign.banner.entity.slug}/${campaign.banner.id}.${campaign.banner.type || 'jpg'}`"
:width="campaign.banner.width"
:height="campaign.banner.height"
class="campaign-banner"
>
</a>
</template>
<script>
function ratioFilter(banner) {
if (!banner) {
return false;
}
if (this.minHeight && banner.height < this.minHeight) {
return false;
}
if (this.maxHeight && banner.height > this.minHeight) {
return false;
}
if (this.minRatio && banner.ratio < this.minRatio) {
return false;
}
if (this.maxRatio && banner.ratio > this.maxRatio) {
return false;
}
return true;
}
function entityCampaign() {
const bannerCampaigns = this.entity.campaigns
.concat(this.entity.children?.flatMap(child => child.campaigns))
.concat(this.entity.parent?.campaigns)
.filter(campaignX => campaignX && this.ratioFilter(campaignX.banner));
if (bannerCampaigns.length > 0) {
const randomCampaign = bannerCampaigns[Math.floor(Math.random() * bannerCampaigns.length)];
this.$emit('campaign', randomCampaign);
return randomCampaign;
}
this.$emit('campaign', null);
return null;
}
function tagCampaign() {
const campaignBanners = this.tag.banners.filter(banner => banner.campaigns.length > 0 && this.ratioFilter(banner));
const banner = campaignBanners[Math.floor(Math.random() * campaignBanners.length)];
if (banner?.campaigns.length > 0) {
const randomCampaign = {
...banner.campaigns[Math.floor(Math.random() * banner.campaigns.length)],
banner,
};
this.$emit('campaign', randomCampaign);
return randomCampaign;
}
this.$emit('campaign', null);
return null;
}
function campaign() {
if (this.entity) {
return this.entityCampaign();
}
if (this.tag) {
return this.tagCampaign();
}
this.$emit('campaign', null); // allow parent to toggle campaigns depending on availability
return null;
}
export default {
props: {
entity: {
type: Object,
default: null,
},
tag: {
type: Object,
default: null,
},
minHeight: {
type: Number,
default: null,
},
maxHeight: {
type: Number,
default: null,
},
minRatio: {
type: Number,
default: null,
},
maxRatio: {
type: Number,
default: null,
},
},
emits: ['campaign'],
computed: {
campaign,
},
methods: {
entityCampaign,
ratioFilter,
tagCampaign,
},
};
</script>
<style lang="scss" scoped>
.campaign {
height: 100%;
display: inline-flex;
flex-grow: 1;
align-items: center;
justify-content: center;
}
.campaign-banner {
height: auto;
max-height: 100%;
max-width: 100%;
}
</style>

View File

@@ -1,33 +1,137 @@
<template>
<div class="container">
<Header />
<div class="container">
<Warning
v-if="showWarning"
class="warning-container"
@enter="(includeQueer) => setConsent(true, includeQueer)"
@leave="setConsent(false)"
/>
<div class="content">
<!-- key forces rerender when new and old path use same component -->
<router-view :key="$route.fullPath" />
</div>
</div>
<transition name="slide">
<Sidebar
v-if="showSidebar"
@toggle-sidebar="(state) => toggleSidebar(state)"
@show-filters="(state) => toggleFilters(state)"
/>
</transition>
<Header
@toggle-sidebar="(state) => toggleSidebar(state)"
@show-filters="(state) => toggleFilters(state)"
/>
<p
v-if="config.showDisclaimer"
class="disclaimer"
>{{ config.disclaimer }}</p>
<div
ref="content"
class="content"
@scroll="scroll"
>
<router-view @scroll="scrollToTop" />
</div>
<Filters
v-if="showFilters"
@close="toggleFilters(false)"
/>
</div>
</template>
<script>
import Warning from './warning.vue';
import Header from '../header/header.vue';
import Sidebar from '../sidebar/sidebar.vue';
import Filters from '../filters/filters.vue';
function toggleSidebar(state) {
this.showSidebar = typeof state === 'boolean' ? state : !this.showSidebar;
}
function toggleFilters(state) {
this.showFilters = state;
this.showSidebar = false;
}
async function setConsent(consent, includeQueer) {
if (consent) {
this.showWarning = false;
localStorage.setItem('consent', window.env.sessionId);
}
if (includeQueer) {
this.$store.dispatch('setTagFilter', this.$store.state.ui.tagFilter.filter((tag) => !['gay', 'bisexual', 'transsexual'].includes(tag)));
return;
}
this.$store.dispatch('setTagFilter', this.$store.state.ui.tagFilter.concat(['gay', 'bisexual', 'transsexual']));
}
function blur(event) {
this.events.emit('blur', event);
}
function resize(event) {
this.events.emit('resize', event);
}
function scroll(event) {
this.events.emit('scroll', event);
}
function scrollToTop() {
this.$refs.content.scrollTop = 0;
}
function mounted() {
document.addEventListener('click', this.blur);
window.addEventListener('resize', this.resize);
}
function beforeUnmount() {
document.removeEventListener('click', this.blur);
window.removeEventListener('resize', this.resize);
}
export default {
components: {
Header,
},
components: {
Header,
Sidebar,
Warning,
Filters,
},
data() {
return {
showSidebar: false,
showWarning: localStorage.getItem('consent') !== window.env.sessionId,
showFilters: false,
};
},
mounted,
beforeUnmount,
methods: {
toggleSidebar,
toggleFilters,
setConsent,
blur,
resize,
scroll,
scrollToTop,
},
};
</script>
<style lang="scss">
@import 'theme';
.container {
background: $background-dim;
position: relative;
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
background: var(--background-soft);
color: var(--text);
}
.content {
@@ -35,11 +139,57 @@ export default {
flex-direction: column;
flex-grow: 1;
overflow-y: auto;
overflow-x: hidden;
}
.content-inner {
flex-grow: 1;
padding: 1rem;
overflow-y: auto;
display: flex;
flex-direction: column;
flex-grow: 1;
overflow-y: auto;
overflow-x: hidden;
z-index: 1;
}
.slide-enter-active,
.slide-leave-active {
&.sidebar-container {
transition: background .15s ease-in-out;
}
.sidebar {
transition: transform .15s ease-in-out;
}
}
.slide-enter-from,
.slide-leave-to {
&.sidebar-container {
background: transparent;
}
.sidebar {
transform: translate(100%, 0);
}
}
.column {
width: 1200px;
max-width: 100%;
padding: 0 1rem;
margin: 0 auto;
box-sizing: border-box;
}
</style>
<style lang="scss" scoped>
.disclaimer {
padding: .5rem 1rem;
margin: 0;
color: var(--text-light);
background: var(--warn);
font-weight: bold;
box-shadow: inset 0 0 3px var(--darken-weak);
text-align: center;
}
</style>

View File

@@ -0,0 +1,268 @@
<template>
<div class="warning-container">
<div class="warning">
<strong class="title">
<span
class="logo"
v-html="logo"
/>contains sexually explicit content
</strong>
<ul class="rules">
<li class="rule">You are at least 18 years old, and legally permitted to view adult material in your jurisdiction.</li>
<li class="rule">You do not regard erotic and frankly pornographic media as obscene or offensive.</li>
<li class="rule">You understand that most sexual scenarios depicted on this website are fictional, performed by professional actors for the purpose of entertainment, and not representative of real-life interactions.</li>
</ul>
<span class="preferences">You can adjust your content preferences later</span>
<div class="actions">
<a
href="https://www.google.com"
class="button leave"
@click="$emit('leave')"
><Icon icon="arrow-left16" />Leave</a>
<button
class="button enter queer"
@click="$emit('enter', true)"
>
<span class="button-title">Enter</span>
<span class="button-sub">I want to see gay, bi and trans content</span>
</button>
<button
class="button enter straight"
@click="$emit('enter', false)"
>
<span class="button-title">Enter</span>
<span class="button-sub">I prefer straight content</span>
</button>
</div>
</div>
</div>
</template>
<script>
import logo from '../../img/logo.svg';
export default {
data() {
return {
logo,
};
},
emits: ['enter'],
};
</script>
<style lang="scss" scoped>
@import 'breakpoints';
.warning-container {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
position: absolute;
z-index: 10;
background: var(--background-censor);
backdrop-filter: blur(.25rem);
overflow-y: auto;
}
.warning {
color: var(--text);
width: 50rem;
max-height: 100%;
max-width: 100%;
margin: 1rem;
}
.copy,
.rules {
padding: .5rem 1rem;
}
.title {
display: block;
font-size: 2rem;
margin: 1rem 0;
color: var(--text);
text-align: center;
}
.logo {
width: 6.5rem;
display: inline-block;
fill: var(--primary);
margin: 0 .6rem 0 0;
::v-deep(svg) {
width: 100%;
height: 100%;
}
}
.copy {
display: block;
padding: 0 1rem;
font-weight: bold;
}
.rules {
margin: 0 0 0 1rem;
text-align: left;
text-shadow: 0 0 3px var(--highlight-strong);
}
.rule {
padding: .5rem 1rem;
line-height: 1.5;
}
.actions {
display: flex;
text-align: center;
padding: 1rem 0;
}
.button {
display: inline-flex;
flex-basis: 0;
flex-direction: column;
align-items: center;
justify-content: center;
border: none;
border-radius: .25rem;
padding: 0;
position: relative;
cursor: pointer;
font-size: 1.5rem;
text-decoration: none;
transition: border-radius .2s ease;
&.leave {
color: var(--shadow-strong);
flex-direction: row;
padding: 1rem;
.icon {
width: 1.5rem;
height: 1.5rem;
margin: 0 1rem 0 0;
fill: var(--shadow);
}
&:hover {
color: var(--text);
}
}
&.enter {
flex-grow: 1;
}
&.straight,
&.queer {
color: var(--lighten-strong);
background: var(--darken-censor);
&:before {
content: '';
width: calc(100% + .3rem);
height: calc(100% + .25rem);
position: absolute;
z-index: -1;
filter: blur(.25rem);
transition: filter .2s ease;
}
&:hover {
color: var(--text-light);
border-radius: 0;
.button-sub {
color: var(--text-light);
}
}
}
&.straight:before {
background: var(--primary);
}
&.queer:before {
background: linear-gradient(90deg, #f00, #f80, #ff0, #0f0, #00f, #a0f, #fff, #f8f, #0ff);
}
&:not(:last-child) {
margin: 0 2rem 0 0;
}
&:hover {
.icon {
fill: var(--text);
}
&.straight:before,
&.queer:before {
filter: blur(0);
}
}
}
.button-title,
.button-sub {
width: 100%;
box-sizing: border-box;
}
.button-title {
font-size: 1.5rem;
padding: .5rem 0 .15rem 0;
font-weight: bold;
}
.button-sub {
display: block;
font-size: .8rem;
padding: .15rem .5rem .75rem .5rem;
color: var(--lighten);
}
.preferences {
color: var(--shadow);
display: block;
padding: .5rem 0 1rem 0;
text-align: center;
font-size: .9rem;
}
@media(max-width: $breakpoint) {
.title {
font-size: 1.75rem;
}
.logo {
width: 5.75rem;
margin: 0 .5rem 0 0;
}
}
@media(max-width: $breakpoint-small) {
.actions {
flex-direction: column-reverse;
padding: 0;
.button {
margin: 0 0 1rem 0;
}
.button:first-child {
margin: 0;
}
}
}
</style>

View File

@@ -0,0 +1,139 @@
<template>
<teleport to="body">
<div
class="container"
@click="$emit('close')"
>
<div
class="dialog"
@click.stop="events.emit('blur')"
>
<div
v-if="title || $slots.header"
class="header"
>
<slot name="header">
<h2 class="header-title">{{ title }}</h2>
</slot>
<Icon
icon="cross2"
class="close"
@click="$emit('close')"
/>
</div>
<div class="body">
<slot />
</div>
</div>
</div>
</teleport>
</template>
<script>
function mounted() {
this.events.emit('blur');
}
export default {
props: {
title: {
type: String,
default: null,
},
},
emits: ['close'],
mounted,
};
</script>
<style lang="scss" scoped>
.container {
display: flex;
align-items: center;
justify-content: center;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
padding: 1rem;
background: var(--darken-strong);
z-index: 10;
}
.dialog {
display: flex;
flex-direction: column;
max-width: 100%;
max-height: 100%;
background: var(--background);
box-shadow: 0 0 3px var(--darken-weak);
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
background: var(--primary);
color: var(--text-light);
font-size: 1.5rem;
font-weight: bold;
}
.header-title {
padding: .5rem .5rem .5rem 1rem;
margin: 0;
overflow: hidden;
text-overflow: ellipsis;
font-size: 1.25rem;
}
.close {
height: 100%;
padding: 0 1rem;
fill: var(--lighten);
&:hover {
fill: var(--lighten-strong);
cursor: pointer;
}
}
.body {
display: flex;
flex-direction: column;
flex-grow: 1;
overflow: hidden;
::v-deep(.section) {
padding: 1rem;
}
}
::v-deep(.dialog-body) {
padding: 1rem;
flex-grow: 1;
overflow-y: auto;
}
::v-deep(.dialog-section:not(:last-child)) {
border-bottom: solid 1px var(--shadow-hint);
overflow: auto;
}
::v-deep(.dialog-actions) {
display: flex;
margin: 1rem 0 0 0;
&.center {
justify-content: center;
}
&.right {
justify-content: flex-end;
}
}
</style>

View File

@@ -0,0 +1,74 @@
<template>
<div class="children">
<div
v-for="child in entity.children"
:key="`child-${child.id}`"
class="tile-container"
>
<EntityTile
:entity="child"
@load="(event) => $emit('load', event)"
/>
</div>
</div>
</template>
<script>
import EntityTile from './tile.vue';
export default {
components: {
EntityTile,
},
emits: ['load'],
props: {
entity: {
type: Object,
default: null,
},
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
.children {
display: flex;
box-sizing: border-box;
padding: 1rem;
margin: 0 1rem 0 0;
border-bottom: solid 1px var(--darken-hint);
.tile-container {
display: inline-block;
padding: 0 1rem 0 0;
}
.tile {
width: 15rem;
height: 6rem;
}
&.expanded {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
grid-gap: 1rem;
margin: 0;
.tile-container {
padding: 0;
}
.tile {
width: 100%;
}
}
}
@media(max-width: $breakpoint0) {
.children.expanded {
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
}
}
</style>

View File

@@ -0,0 +1,333 @@
<template>
<div
v-if="entity"
class="entity content"
>
<div class="info">
<a
:href="entityUrl"
target="_blank"
rel="noopener"
class="link link-child"
>
<template v-if="entity.hasLogo">
<img
v-if="$route.name === 'network'"
class="logo logo-child"
:src="`/img/logos/${entity.slug}/thumbs/network.png`"
>
<img
v-else-if="entity.parent && !entity.independent"
class="logo logo-child"
:src="`/img/logos/${entity.parent.slug}/thumbs/${entity.slug}.png`"
>
<img
v-else
class="logo logo-child"
:src="`/img/logos/${entity.slug}/thumbs/${entity.slug}.png`"
>
</template>
<h2
v-else
class="name"
>{{ entity.name }}</h2>
<Icon
v-if="entity.url"
icon="share2"
/>
</a>
<ul
v-if="entity.tags.length > 0"
class="tags"
>
<li
v-for="tag in entity.tags"
:key="`tag-${tag.slug}`"
>{{ tag.name }}</li>
</ul>
<RouterLink
v-if="entity.parent"
:to="`/${entity.parent.type}/${entity.parent.slug}`"
class="link link-parent"
>
<img
v-if="entity.parent.hasLogo"
class="logo logo-parent"
:src="`/img/logos/${entity.parent.slug}/thumbs/network.png`"
>
<img
v-if="entity.parent.hasLogo"
class="favicon"
:src="`/img/logos/${entity.parent.slug}/favicon.png`"
>
<h3
v-else
class="name parent-name"
>{{ entity.parent.name }}</h3>
<Icon icon="device_hub" />
</RouterLink>
</div>
<div
class="content-inner"
@scroll="events.emit('scroll', $event)"
>
<Scroll
v-if="entity.children.length > 0"
v-slot="scroll"
:expandable="true"
:expanded="expanded"
class="scroll-light"
@expand="(state) => expanded = state"
>
<Children
:entity="entity"
:class="{ expanded }"
@load="scroll.loaded"
/>
</Scroll>
<div class="campaign-container">
<Campaign
:entity="entity"
:min-ratio="3"
/>
</div>
<FilterBar
ref="filter"
:fetch-releases="fetchEntity"
:items-total="totalCount"
:items-per-page="limit"
:available-tags="entity.tags"
/>
<div class="releases">
<Releases
:releases="entity.releases"
:done="done"
/>
<Pagination
:items-total="totalCount"
:items-per-page="limit"
class="pagination-top"
/>
</div>
<Footer />
</div>
</div>
</template>
<script>
import FilterBar from '../filters/filter-bar.vue';
import Pagination from '../pagination/pagination.vue';
import Releases from '../releases/releases.vue';
import Children from './children.vue';
import Scroll from '../scroll/scroll.vue';
import Campaign from '../campaigns/campaign.vue';
async function fetchEntity(scroll = true) {
this.done = false;
this.entityUrl = null;
const { entity, totalCount } = await this.$store.dispatch('fetchEntityBySlugAndType', {
entitySlug: this.$route.params.entitySlug,
entityType: this.$route.name,
limit: this.limit,
range: this.$route.params.range,
pageNumber: Number(this.$route.params.pageNumber),
});
this.entity = entity;
this.totalCount = totalCount;
this.pageTitle = entity.name;
const campaign = entity.campaigns.find((campaignX) => !campaignX.banner)
|| entity.parent?.campaigns.find((campaignX) => !campaignX.banner);
const affiliateParams = new URLSearchParams({
...(entity.url && Object.fromEntries(new URL(entity.url).searchParams)), // preserve any query in entity URL, e.g. ?siteId=5
...(campaign?.affiliate?.parameters && Object.fromEntries(new URLSearchParams(campaign.affiliate.parameters))), // append affiliate parameters
}).toString();
this.entityUrl = campaign?.url || campaign?.affiliate?.url || `${entity.url}${campaign?.affiliate?.parameters ? `?${affiliateParams}` : ''}`;
this.done = true;
if (scroll && this.$refs.filter?.$el) {
this.$refs.filter.$el.scrollIntoView();
}
}
async function mounted() {
await this.fetchEntity();
}
async function route(to) {
if (to.name === 'channel' || to.name === 'network' || to.name === 'studio') {
await this.fetchEntity();
this.expanded = false;
}
}
export default {
components: {
FilterBar,
Pagination,
Children,
Releases,
Scroll,
Campaign,
},
data() {
return {
entity: null,
pageTitle: null,
totalCount: null,
done: false,
limit: Number(this.$route.query.limit) || 20,
expanded: false,
entityUrl: null,
};
},
watch: {
$route: route,
'$store.state.ui.tagFilter': fetchEntity,
},
mounted,
methods: {
fetchEntity,
},
};
</script>
<style lang="scss" scoped>
@import 'breakpoints';
.info {
display: flex;
justify-content: space-between;
background: var(--profile);
border-bottom: solid 1px var(--lighten-hint);
}
.link {
max-width: 15rem;
display: flex;
align-items: center;
box-sizing: border-box;
padding: 1rem;
text-decoration: none;
}
.link-child {
flex-shrink: 0;
.icon {
fill: var(--lighten);
margin: 0 0 0 1rem;
}
&:hover .icon {
fill: var(--text-light);
}
}
.link-parent {
flex-direction: row-reverse;
flex-shrink: 0;
margin: 0 0 0 3rem;
.icon {
width: 1.25rem;
height: 1.25rem;
fill: var(--lighten);
margin: 0 .5rem 0 0;
}
}
.logo {
height: 100%;
max-width: 100%;
object-fit: contain;
}
.logo-child {
height: 3rem;
}
.logo-parent {
height: 1.5rem;
}
.favicon {
height: 1rem;
flex-shrink: 0;
}
.content-inner {
display: flex;
flex-direction: column;
}
.scroll {
background: var(--profile);
}
.campaign-container {
background: var(--background-dim);
text-align: center;
padding: .5rem;
}
.releases {
flex-grow: 1;
}
.name {
color: var(--text-light);
display: flex;
align-items: center;
padding: 0;
margin: 0;
white-space: nowrap;
font-size: 1.5rem;
}
.favicon {
display: none;
}
@media(max-width: $breakpoint) {
.tags {
display: none;
}
}
@media(max-width: $breakpoint-micro) {
.logo-parent {
display: none;
}
.favicon {
display: inline-block;
}
}
@media(max-width: $breakpoint-nano) {
.link-child .icon {
display: none;
}
}
</style>

View File

@@ -0,0 +1,127 @@
<template>
<router-link
:to="`/${entity.type}/${entity.slug}`"
:title="entity.name"
class="tile"
>
<div class="tile-logo">
<template v-if="entity.hasLogo">
<img
v-if="entity.type === 'network' || entity.independent"
:src="`/img/logos/${entity.slug}/thumbs/network.png`"
:alt="entity.name"
loading="lazy"
class="logo"
@load="$emit('load', $event)"
>
<img
v-else-if="entity.parent"
:src="`/img/logos/${entity.parent.slug}/thumbs/${entity.slug}.png`"
:alt="entity.name"
loading="lazy"
class="logo"
@load="$emit('load', $event)"
>
<img
v-else
:src="`/img/logos/${entity.slug}/thumbs/${entity.slug}.png`"
:alt="entity.name"
loading="lazy"
class="logo"
@load="$emit('load', $event)"
>
</template>
<span
v-else
class="name"
>{{ entity.name }}</span>
</div>
<span
v-if="entity.childrenTotal > 0 || typeof entity.sceneTotal !== 'undefined'"
class="count"
>
<span v-if="typeof entity.sceneTotal !== 'undefined'">{{ entity.sceneTotal }} scenes</span>
<span v-if="entity.type === 'network'">{{ entity.childrenTotal }} channels</span>
</span>
</router-link>
</template>
<script>
export default {
props: {
entity: {
type: Object,
default: null,
},
},
emits: ['load'],
};
</script>
<style lang="scss" scoped>
@import 'theme';
.tile {
height: 100%;
background: var(--tile);
display: flex;
flex-direction: column;
flex-shrink: 0;
justify-content: center;
align-items: center;
box-sizing: border-box;
border-radius: .25rem;
position: relative;
box-shadow: 0 0 3px rgba(0, 0, 0, .25);
text-align: center;
text-decoration: none;
overflow: hidden;
&:hover .count {
bottom: 0;
}
}
.tile-logo {
display: flex;
flex-grow: 1;
padding: .5rem 1rem;
overflow: hidden;
}
.logo {
max-width: 100%;
max-height: 100%;
display: flex;
align-items: center;
align-self: center;
}
.name {
display: flex;
align-items: center;
color: var(--text-light);
font-size: 1.25rem;
font-weight: bold;
}
.count {
display: flex;
justify-content: space-between;
width: 100%;
position: absolute;
bottom: -1.75rem;
box-sizing: border-box;
padding: .25rem .5rem;
background: var(--darken-strong);
box-shadow: 0 0 3px var(--darken);
color: var(--text-light);
text-align: center;
text-shadow: 1px 1px var(--darken);
transition: bottom .1s ease;
}
</style>

View File

@@ -1,20 +1,20 @@
<template>
<div class="errorpage">
<h1 class="error">404 - Not Found</h1>
<div class="errorpage">
<h1 class="error">404 - Not Found</h1>
<a
href="/"
class="home"
>Take me home</a>
</div>
<a
href="/"
class="home"
>Take me home</a>
</div>
</template>
<style lang="scss" scoped>
@import 'theme';
.errorpage {
background: $background;
color: $primary;
background: var(--background);
color: var(--primary);
height: 100%;
display: flex;
flex-direction: column;
@@ -28,7 +28,7 @@
}
.home {
color: $shadow;
color: var(--shadow);
margin: 3rem 0;
}
</style>

View File

@@ -0,0 +1,63 @@
<template>
<div>
<div
v-show="expanded"
class="expand-button expanded noselect"
@click="$emit('expand', false)"
><Icon icon="arrow-up3" /></div>
<div
v-show="!expanded"
class="expand-button noselect"
@click="$emit('expand', true)"
><Icon icon="arrow-down3" /></div>
</div>
</template>
<script>
export default {
props: {
expanded: {
type: Boolean,
default: false,
},
},
};
</script>
<style lang="scss" scoped>
.expand-button {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: .5rem;
.icon {
fill: var(--lighten);
}
&:hover {
cursor: pointer;
background: var(--lighten-hint);
.icon {
fill: var(--text-light);
}
}
}
.expand-dark {
.icon {
fill: var(--darken-weak);
}
&:hover {
background: var(--darken-hint);
.icon {
fill: var(--darken);
}
}
}
</style>

View File

@@ -0,0 +1,88 @@
<template>
<Tooltip class="filter-container">
<div class="filter">
<Icon icon="users" />
<div
v-if="selectedActors.length > 0"
class="filter-applied"
>{{ selectedActors.length }} {{ selectedActors.length > 1 ? 'actors' : 'actor' }}</div>
<div
v-else
class="filter-applied empty"
>Actors</div>
</div>
<template v-slot:tooltip>
<div @click.stop>
<router-link
class="filter-clear"
:to="{ query: { ...$route.query, actors: undefined } }"
:class="{ active: selectedActors.length > 0 }"
>clear all<Icon icon="cross2" /></router-link>
<ul class="filter-items nolist">
<li
v-for="actor in availableActors"
:key="actor.id"
class="filter-item"
:class="{ selected: selectedActors.includes(actor.slug) }"
>
<router-link
:to="{ query: {
...$route.query,
actors: actor.slug,
}, params: { pageNumber: 1 } }"
class="filter-name"
>{{ actor.name }}</router-link>
<router-link
:to="{ query: { ...$route.query, ...getNewRange(actor) }, params: { pageNumber: 1 } }"
class="filter-include"
>
<Icon
icon="checkmark"
class="filter-add"
/>
<Icon
icon="cross2"
class="filter-remove"
/>
</router-link>
</li>
</ul>
</div>
</template>
</Tooltip>
</template>
<script>
function getNewRange(actor) {
if (this.selectedActors.includes(actor.slug)) {
return { actors: this.selectedActors.filter(selectedActor => selectedActor !== actor.slug).join(',') || undefined };
}
return { actors: this.selectedActors.concat(actor.slug).join(',') };
}
function selectedActors() {
return this.$route.query.actors ? this.$route.query.actors.split(',') : [];
}
export default {
props: {
availableActors: {
type: Array,
default: () => [],
},
},
computed: {
selectedActors,
},
methods: {
getNewRange,
},
};
</script>

View File

@@ -0,0 +1,179 @@
<template>
<Tooltip class="filter-container">
<div class="filter">
<Icon icon="antenna" />
<div
v-if="selectedLength > 0"
class="filter-applied"
>{{ selectedLength }} {{ selectedLength > 1 ? 'channels' : 'channel' }}</div>
<div
v-else
class="filter-applied empty"
>Channels</div>
</div>
<template v-slot:tooltip>
<div @click.stop>
<router-link
class="filter-clear"
:to="{ query: { ...$route.query, channels: undefined, networks: undefined } }"
:class="{ active: selectedLength > 0 }"
>clear all<Icon icon="cross2" /></router-link>
<ul class="filter-items nolist">
<li
v-for="channel in channelsPerNetwork"
:key="`channel-${channel.id}`"
class="filter-item"
:class="{
[channel.type]: true,
independent: channel.independent,
selected: channel.type === 'network' ? selectedNetworks.includes(channel.slug) : selectedChannels.includes(channel.slug),
disabled: channel.parent && selectedNetworks.includes(channel.parent.slug),
}"
>
<router-link
:to="{ query: {
...$route.query,
[channel.type === 'network' ? 'networks' : 'channels']: channel.slug,
[channel.type === 'network' ? 'channels' : 'networks']: undefined,
}, params: { pageNumber: 1 } }"
class="filter-name"
>
<img
v-if="channel.type === 'network' || channel.independent || !channel.parent "
:src="`/img/logos/${channel.slug}/favicon.png`"
class="favicon"
>
{{ channel.name }}
</router-link>
<router-link
:to="{ query: { ...$route.query, ...getNewRange(channel) }, params: { pageNumber: 1 } }"
class="filter-include"
>
<Icon
icon="checkmark"
class="filter-add"
/>
<Icon
icon="cross2"
class="filter-remove"
/>
</router-link>
</li>
</ul>
</div>
</template>
</Tooltip>
</template>
<script>
function getNewRange(entity) {
if (entity.type === 'network' && this.selectedNetworks.includes(entity.slug)) {
return {
channels: this.selectedChannels.join(',') || undefined,
networks: this.selectedNetworks.filter(selectedTag => selectedTag !== entity.slug).join(',') || undefined,
};
}
if (entity.type === 'network') {
return {
channels: this.selectedChannels.join(',') || undefined,
networks: this.selectedNetworks.concat(entity.slug).join(','),
};
}
if (this.selectedChannels.includes(entity.slug)) {
return {
channels: this.selectedChannels.filter(selectedTag => selectedTag !== entity.slug).join(',') || undefined,
networks: this.selectedNetworks.join(',') || undefined,
};
}
return {
channels: this.selectedChannels.concat(entity.slug).join(','),
networks: this.selectedNetworks.join(',') || undefined,
};
}
function selectedChannels() {
return this.$route.query.channels ? this.$route.query.channels.split(',') : [];
}
function selectedNetworks() {
return this.$route.query.networks ? this.$route.query.networks.split(',') : [];
}
function channelsPerNetwork() {
const networks = this.availableChannels.reduce((acc, channel) => {
if (channel.type === 'network' || channel.independent || !channel.parent) {
acc[channel.slug] = { ...channel, children: [] };
return acc;
}
if (!acc[channel.parent.slug]) {
acc[channel.parent.slug] = { ...channel.parent, children: [] };
}
acc[channel.parent.slug].children.push(channel);
return acc;
}, {});
return Object.values(networks).reduce((acc, network) => [...acc, network, ...(network.children || [])], []);
}
function selectedLength() {
return this.selectedChannels.length + this.selectedNetworks.length;
}
export default {
props: {
filter: {
type: Array,
default: () => [],
},
compact: {
type: Boolean,
default: false,
},
availableChannels: {
type: Array,
default: () => [],
},
},
computed: {
channelsPerNetwork,
selectedChannels,
selectedNetworks,
selectedLength,
},
methods: {
getNewRange,
},
};
</script>
<style lang="scss" scoped>
.favicon {
width: 1rem;
height: 1rem;
padding: 0 .75rem 0 0;
filter: drop-shadow(0 0 1px var(--darken));
}
.network .filter-name,
.independent .filter-name {
font-weight: bold;
padding: .5rem;
}
.channel:not(.independent) .filter-name {
padding: .5rem .5rem .5rem 2.25rem;
}
</style>

View File

@@ -0,0 +1,366 @@
<template>
<div class="filter-bar noselect">
<div class="sort">
<RouterLink
v-for="section in ranges"
:key="section"
:to="{ params: { range: section, pageNumber: 1 }, query: $route.query }"
:class="{ active: $route.name === section || range === section }"
class="range range-button"
>{{ section }}</RouterLink>
</div>
<span
v-if="itemsTotal && showTotal"
class="total"
>{{ itemsTotal }} <template v-if="itemsTotal === 1">scene</template><template v-else>scenes</template></span>
<div class="filters">
<ActorFilter
class="filters-filter"
:available-actors="availableActors"
/>
<ChannelFilter
class="filters-filter"
:available-channels="availableChannels"
/>
<TagFilter
class="filters-filter"
:available-tags="availableTags"
/>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex';
import ActorFilter from './actor-filter.vue';
import ChannelFilter from './channel-filter.vue';
import TagFilter from './tag-filter.vue';
function range() {
return this.$route.params.range;
}
function batch(state) {
return state.ui.batch;
}
async function setRange(newRange) {
this.$store.dispatch('setRange', newRange);
await this.fetchReleases();
}
async function setBatch(newBatch) {
this.$store.dispatch('setBatch', newBatch);
await this.fetchReleases();
}
export default {
components: {
ActorFilter,
ChannelFilter,
TagFilter,
},
props: {
fetchReleases: {
type: Function,
default: null,
},
isHome: {
type: Boolean,
default: false,
},
showTotal: {
type: Boolean,
default: true,
},
itemsTotal: {
type: Number,
default: 0,
},
itemsPerPage: {
type: Number,
default: 10,
},
ranges: {
type: Array,
default: () => ['latest', 'upcoming', 'new'],
},
availableTags: {
type: Array,
default: () => [],
},
availableChannels: {
type: Array,
default: () => [],
},
availableActors: {
type: Array,
default: () => [],
},
},
computed: {
...mapState({
range,
batch,
}),
},
methods: {
setRange,
setBatch,
},
};
</script>
<style lang="scss">
@import 'breakpoints';
.filter {
color: var(--shadow);
display: inline-flex;
align-items: center;
.filter-applied {
flex-grow: 1;
padding: .75rem .5rem;
font-size: 1rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-align: right;
&.empty {
color: var(--shadow);
}
}
.icon {
fill: var(--shadow);
margin: -.1rem 0 0 0;
}
&:hover {
cursor: pointer;
.applied {
color: var(--shadow-strong);
}
.icon {
fill: var(--shadow-strong);
}
}
}
.filter-mode {
width: 100%;
color: var(--shadow);
background: none;
padding: .75rem;
margin: 0 0 .5rem 0;
font-size: 1rem;
border: none;
border-bottom: solid 1px var(--shadow-hint);
option {
color: var(--text-dark);
}
}
.filter-clear {
display: flex;
align-items: center;
justify-content: space-between;
padding: .5rem 1rem;
color: var(--shadow-weak);
text-decoration: none;
cursor: default;
.icon {
fill: var(--shadow-hint);
margin: 0 0 0 1rem;
}
&.active {
color: var(--shadow);
.icon {
fill: var(--shadow-weak);
}
&:hover {
color: var(--text);
background: var(--shadow-hint);
cursor: pointer;
.icon {
fill: var(--alert);
}
}
}
}
.filter-items .filter-item {
display: flex;
align-items: center;
&:hover {
background: var(--shadow-hint);
cursor: pointer;
}
&.selected {
.filter-include {
.filter-add {
fill: var(--success);
}
&:hover {
.filter-add {
display: none;
}
.filter-remove {
display: inline-block;
}
}
}
}
}
.filter-include {
.icon {
width: 1rem;
height: 1rem;
padding: .5rem 1rem;
fill: var(--shadow-hint);
}
.filter-remove {
display: none;
fill: var(--alert);
}
&:hover {
cursor: pointer;
}
}
.filter-name {
min-width: 8rem;
display: flex;
flex-grow: 1;
padding: .5rem .75rem .5rem 1rem;
color: var(--text);
text-decoration: none;
}
.filter-include:hover,
.filter-name:hover {
background: var(--shadow-hint);
}
@media(max-width: $breakpoint-small) {
.filter-applied {
display: none;
}
.filters-filter:not(:last-child) .filter {
padding: .5rem;
}
.filters-filter:last-child .filter {
padding: .5rem 0 .5rem .5rem;
}
.total {
display: none;
}
}
</style>
<style lang="scss" scoped>
@import 'theme';
.filter-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: .5rem 1rem 0 1rem;
z-index: 1;
background: var(--background-dim);
font-size: 0;
.icon {
margin: 0 .5rem 0 0;
fill: var(--shadow);
}
}
.filters {
flex-shrink: 0;
}
.sort {
display: flex;
align-items: center;
}
.range-button {
color: var(--shadow);
display: inline-block;
padding: .8rem 1rem;
border: none;
position: relative;
font-size: .8rem;
font-weight: bold;
text-decoration: none;
border: solid 1px transparent;
border-bottom: none;
text-transform: capitalize;
&:hover:not(.active) {
color: var(--shadow-strong);
cursor: pointer;
}
&.active {
color: var(--primary);
background: var(--background-soft);
border-color: var(--crease);
&::after {
/* hide grey border for tab effect, negative margin shows grey crease on mobile */
content: '';
width: 100%;
height: 2px;
background: var(--background-soft);
position: absolute;
left: 0;
bottom: -2px;
}
}
}
.filters-filter {
display: inline-block;
flex-shrink: 0;
&:not(:last-child) {
margin: 0 1rem 0 0;
}
}
.total {
flex-shrink: 0;
padding: 0 1rem;
color: var(--shadow);
font-size: .8rem;
}
</style>

View File

@@ -0,0 +1,84 @@
<template>
<Dialog
title="Filters"
@close="$emit('close')"
>
<div class="filters dialog-body">
<h3 class="form-heading">Show me</h3>
<ul class="tags nolist">
<li
v-for="tag in tags"
:key="tag"
class="tags-item"
>
<Checkbox
:checked="!tagFilter.includes(tag)"
:label="tag"
class="tag"
@change="(state) => filterTag(tag, state)"
/>
</li>
</ul>
<p class="disclaimer">You may still incidentally see filtered out content</p>
</div>
</Dialog>
</template>
<script>
import Checkbox from '../form/checkbox.vue';
function tagFilter() {
return this.$store.state.ui.tagFilter;
}
function filterTag(tag, isChecked) {
if (isChecked) {
this.$store.dispatch('setTagFilter', this.tagFilter.filter(filteredTag => filteredTag !== tag));
} else {
this.$store.dispatch('setTagFilter', this.tagFilter.concat(tag));
}
}
export default {
components: {
Checkbox,
},
data() {
return {
tags: ['anal', 'gay', 'transsexual', 'bisexual', 'pissing', 'anal prolapse'],
};
},
computed: {
tagFilter,
},
emits: ['close'],
methods: {
filterTag,
},
};
</script>
<style lang="scss" scoped>
.filters {
width: 20rem;
max-width: 100%;
}
.tags-item {
display: block;
}
.tag {
padding: .5rem 0;
}
.disclaimer {
margin: 1rem 0 0 0;
line-height: 1.5;
text-align: center;
font-size: .9rem;
color: var(--shadow);
}
</style>

View File

@@ -0,0 +1,112 @@
<template>
<Tooltip class="filter-container">
<div class="filter">
<Icon icon="price-tag4" />
<div
v-if="selectedTags.length > 0"
class="filter-applied"
>{{ selectedTags.length }} {{ selectedTags.length > 1 ? 'tags' : 'tag' }}</div>
<div
v-else
class="filter-applied empty"
>Tags</div>
</div>
<template #tooltip>
<div
class="filter-options"
@click.stop
>
<select
v-model="mode"
class="filter-mode"
@change="$router.push({ query: { ...$route.query, mode }, params: { pageNumber: 1 } })"
>
<option value="all">match all selected</option>
<option value="any">match any selected</option>
</select>
<router-link
class="filter-clear"
:to="{ query: { ...$route.query, tags: undefined, mode: undefined } }"
:class="{ active: selectedTags.length > 0 }"
>clear all<Icon icon="cross2" /></router-link>
<ul class="filter-items nolist">
<li
v-for="tag in availableTags"
:key="`tag-${tag.id}`"
class="filter-item"
:class="{ selected: selectedTags.includes(tag.slug) }"
>
<router-link
:to="{ query: { ...$route.query, tags: tag.slug, mode }, params: { pageNumber: 1 } }"
class="filter-name"
>{{ tag.name }}</router-link>
<router-link
:to="{ query: { ...$route.query, ...getNewRange(tag.slug), mode }, params: { pageNumber: 1 } }"
class="filter-include"
>
<Icon
icon="checkmark"
class="filter-add"
/>
<Icon
icon="cross2"
class="filter-remove"
/>
</router-link>
</li>
</ul>
</div>
</template>
</Tooltip>
</template>
<script>
function getNewRange(tag) {
if (this.selectedTags.includes(tag)) {
return { tags: this.selectedTags.filter((selectedTag) => selectedTag !== tag).join(',') || undefined };
}
return { tags: this.selectedTags.concat(tag).join(',') };
}
function selectedTags() {
return this.$route.query.tags ? this.$route.query.tags.split(',') : [];
}
export default {
props: {
compact: {
type: Boolean,
default: false,
},
availableTags: {
type: Array,
default: () => [],
},
},
data() {
return {
mode: this.$route.query.mode || 'all',
};
},
computed: {
selectedTags,
},
methods: {
getNewRange,
},
};
</script>
<style lang="scss" scoped>
.filter-options {
width: 15rem;
}
</style>

View File

@@ -0,0 +1,60 @@
<template>
<footer class="footer">
<span class="segment">© traxxx</span>
<router-link
:to="{ name: 'stats' }"
class="segment footer-link nolink"
>stats</router-link>
<a
v-if="config.discord"
:href="config.discord"
target="_blank"
rel="noopener noreferrer"
class="segment footer-link nolink discord"
><Icon icon="discord" /></a>
</footer>
</template>
<style lang="scss" scoped>
.footer {
display: flex;
justify-content: center;
align-items: center;
background: var(--background-dim);
color: var(--shadow);
font-size: .8rem;
font-weight: bold;
box-shadow: inset -3px 0 3px var(--shadow-hint);
}
.segment {
display: inline-flex;
align-items: center;
padding: .5rem;
&:not(:last-child) {
border-right: solid 1px var(--shadow-hint);
}
}
.footer-link {
text-decoration: underline;
&:hover {
color: var(--primary);
.icon {
fill: var(--primary);
}
}
}
.discord {
.icon {
fill: var(--shadow);
width: 4rem;
}
}
</style>

View File

@@ -0,0 +1,102 @@
<template>
<label class="check-container noselect">
<span
v-if="label"
class="check-label"
>{{ label }}</span>
<input
v-show="false"
:id="`checkbox-${uid}`"
:checked="checked"
type="checkbox"
class="check-checkbox"
@change="$emit('change', $event.target.checked)"
>
<label
:for="`checkbox-${uid}`"
class="check"
/>
</label>
</template>
<script>
export default {
props: {
checked: {
type: Boolean,
default: false,
},
label: {
type: String,
default: null,
},
},
emits: ['change'],
};
</script>
<style lang="scss" scoped>
@import 'breakpoints';
.check-container {
display: flex;
justify-content: space-between;
cursor: pointer;
}
.check {
width: 1.25rem;
height: 1.25rem;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
position: relative;
background-color: var(--shadow-hint);
cursor: pointer;
transition: background .15s ease;
&::after {
content: '';
width: .5rem;
height: .3rem;
border: solid 2px var(--text-light);
border-top: none;
border-right: none;
margin: -.2rem 0 0 0;
transform: rotateZ(-45deg) scaleX(0);
transition: transform .15s ease;
}
}
.check-cross .check::before {
content: '';
width: 100%;
height: 100%;
position: absolute;
background: url('/img/icons/cross3.svg') no-repeat center/80%;
opacity: .15;
transition: transform .1s ease;
}
.check-checkbox:checked + .check {
background: var(--primary);
&::after {
transform: rotateZ(-45deg) scaleX(1);
}
&::before {
transform: scaleX(0);
}
}
.check-label {
overflow: hidden;
text-transform: capitalize;
text-overflow: ellipsis;
margin: 0 .5rem 0 0;
}
</style>

View File

@@ -0,0 +1,245 @@
<template>
<div class="range-container">
<div
class="label label-start"
:class="{ disabled }"
@click="setValue('valueA', min)"
>
<slot name="start" />
</div>
<div
class="range"
:class="{ disabled }"
:style="{ background: `linear-gradient(90deg, var(--slider-track) ${minPercentage}%, var(--slider-range) ${minPercentage}%, var(--slider-range) ${maxPercentage}%, var(--slider-track) ${maxPercentage}%)` }"
@click="setNearest"
>
<input
v-model.number="valueA"
:min="min"
:max="max"
:data-value="valueA"
:disabled="disabled"
type="range"
class="slider"
@input="emit('input')"
@change="emit('change')"
@click.stop
>
<input
v-model.number="valueB"
:min="min"
:max="max"
:data-value="valueB"
:disabled="disabled"
type="range"
class="slider"
@input="emit('input')"
@change="emit('change')"
@click.stop
>
</div>
<div
class="label label-end"
:class="{ disabled }"
@click="setValue('valueB', max)"
>
<slot name="end" />
</div>
</div>
</template>
<script>
import { nextTick } from 'vue';
function minValue() {
return Math.min(this.valueA, this.valueB);
}
function maxValue() {
return Math.max(this.valueA, this.valueB);
}
function minPercentage() {
return ((this.minValue - this.min) / (this.max - this.min)) * 100;
}
function maxPercentage() {
return ((this.maxValue - this.min) / (this.max - this.min)) * 100;
}
function emit(type = 'change') {
if (this.values) {
this.$emit(type, [this.values[this.minValue], this.values[this.maxValue]]);
return;
}
this.$emit(type, [this.minValue, this.maxValue]);
}
function setNearest(event) {
if (this.allowEnable) {
this.emit('enable');
}
nextTick(() => {
if (!this.disabled) {
const closestValue = Math.round((event.offsetX / event.target.getBoundingClientRect().width) * (this.max - this.min)) + this.min;
const closestSlider = Math.abs(this.valueA - closestValue) < Math.abs(this.valueB - closestValue) ? 'valueA' : 'valueB';
this[closestSlider] = closestValue;
this.emit();
}
});
}
function setValue(prop, value) {
if (this.allowEnable) {
this.emit('enable');
}
nextTick(() => {
if (!this.disabled) {
this[prop] = value;
this.emit();
}
});
}
export default {
props: {
min: {
type: Number,
default: 0,
},
max: {
type: Number,
default: 10,
},
value: {
type: Array,
default: () => [3, 7],
},
values: {
type: Array,
default: null,
},
disabled: {
type: Boolean,
default: false,
},
allowEnable: {
type: Boolean,
default: true,
},
},
emits: ['change', 'input', 'enable'],
data() {
if (this.values) {
return {
valueA: this.values.indexOf(this.value[0]),
valueB: this.values.indexOf(this.value[1]),
};
}
return {
valueA: this.value[0],
valueB: this.value[1],
};
},
computed: {
minValue,
maxValue,
minPercentage,
maxPercentage,
},
methods: {
emit,
setNearest,
setValue,
},
};
</script>
<style lang="scss">
.dark .range-container .range {
--slider-range: var(--lighten-weak);
}
</style>
<style lang="scss" scoped>
@mixin thumb {
appearance: none;
display: block;
width: 1.25rem;
height: 1.25rem;
border: none;
border-radius: 50%;
background: var(--slider-thumb);
pointer-events: visible;
cursor: pointer;
box-shadow: 0 0 3px var(--darken-weak);
}
.range-container {
display: flex;
justify-content: space-between;
}
.range {
--slider-track: var(--shadow-hint);
--slider-range: var(--primary-faded);
--slider-thumb: var(--primary);
position: relative;
height: 1.25rem;
flex-grow: 1;
border-radius: .625rem;
&.disabled {
--slider-range: var(--shadow-weak);
--slider-thumb: var(--disabled-handle);
}
}
.slider {
width: 100%;
top: 0;
margin: 0;
appearance: none;
position: absolute;
background: none;
outline: none;
pointer-events: none;
}
.slider::-webkit-slider-thumb {
@include thumb;
}
.slider::-moz-range-thumb {
@include thumb;
transform: translateY(2px);
}
.label {
padding: 0 .5rem;
&:hover:not(.disabled) {
cursor: pointer;
::v-deep(.icon) {
fill: var(--primary);
}
}
}
::v-deep(.icon) {
width: 1.25rem;
height: 1.25rem;
flex-shrink: 0;
fill: var(--shadow);
}
</style>

View File

@@ -0,0 +1,115 @@
<template>
<label
class="toggle-container noselect"
:class="{ light: $store.state.ui.theme === 'dark' }"
>
<input
:id="`toggle-${id}`"
:checked="checked"
:true-value="trueValue"
:false-value="falseValue"
:disabled="disabled"
type="checkbox"
class="toggle-input"
@change="$emit('change', $event.target.checked)"
>
<label
:for="`toggle-${id}`"
class="toggle"
/>
</label>
</template>
<script>
export default {
props: {
checked: {
type: Boolean,
default: false,
},
trueValue: {
type: null,
default: true,
},
falseValue: {
type: null,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
},
emits: ['change'],
data() {
return {
id: Math.floor(new Date().getTime() * Math.random()),
};
},
};
</script>
<style lang="scss" scoped>
@import 'breakpoints';
.toggle-container {
display: inline-block;
cursor: pointer;
&.light {
.toggle {
background: var(--lighten-weak);
}
.toggle-input:checked + .toggle {
background: var(--lighten);
}
}
}
.toggle {
width: 2rem;
height: .9rem;
display: flex;
align-items: center;
position: relative;
background: var(--shadow-hint);
border-radius: 1rem;
cursor: pointer;
&::after {
content: '';
background-color: var(--background-light);
width: 1rem;
height: 1rem;
display: inline-block;
position: absolute;
left: 0;
border-radius: 50%;
box-shadow: 0 0 2px var(--darken-strong);
transition: background-color .2s ease, left .2s ease;
}
}
.toggle-input {
display: none;
&:checked + .toggle {
background: var(--primary-faded);
&::after {
background: var(--primary);
left: calc(100% - 1rem);
}
}
&[disabled] + .toggle {
background: var(--shadow-weak);
&::after {
background: var(--shadow);
}
}
}
</style>

View File

@@ -1,186 +0,0 @@
<template>
<div class="filter-bar noselect">
<span>
<label class="range">
<input
:id="`${_uid}-new`"
:checked="range === 'new'"
type="radio"
class="range-input"
@click="setRange('new')"
>
<label
:for="`${_uid}-new`"
class="range-button"
>New</label>
</label>
<label class="range">
<input
:id="`${_uid}-upcoming`"
:checked="range === 'upcoming'"
type="radio"
class="range-input"
@click="setRange('upcoming')"
>
<label
:for="`${_uid}-upcoming`"
class="range-button"
>Upcoming</label>
</label>
<label class="range">
<input
:id="`${_uid}-all`"
:checked="range === 'all'"
type="radio"
class="range-input"
@click="setRange('all')"
>
<label
:for="`${_uid}-all`"
class="range-button"
>All</label>
</label>
</span>
<span>
<span class="filters-container">
<Icon icon="filter" />
<Filters
class="filters-block"
:filter="filter"
@set-filter="setFilter"
/>
</span>
<v-popover class="filters-compact">
<Icon icon="filter" />
<div slot="popover">
<Filters
:compact="true"
:filter="filter"
@set-filter="setFilter"
/>
</div>
</v-popover>
</span>
</div>
</template>
<script>
import { mapState } from 'vuex';
import Filters from './filters.vue';
function filter(state) {
return state.ui.filter;
}
function range(state) {
return state.ui.range;
}
async function setFilter(newFilter) {
this.$store.dispatch('setFilter', newFilter);
await this.fetchReleases();
}
async function setRange(newRange) {
this.$store.dispatch('setRange', newRange);
await this.fetchReleases();
}
export default {
components: {
Filters,
},
props: {
fetchReleases: {
type: Function,
default: null,
},
},
computed: {
...mapState({
filter,
range,
}),
},
methods: {
setFilter,
setRange,
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
.filter-bar {
background: $background;
display: flex;
justify-content: space-between;
align-items: center;
padding: .5rem 1rem;
z-index: 1;
font-size: 0;
box-shadow: 0 0 3px $shadow;
.icon {
fill: $shadow;
}
}
.filters-container {
display: inline-block;
}
.filters-block {
display: inline-block;
}
.filters-compact {
font-size: 1rem;
font-weight: bold;
display: none;
margin: 0 0 0 .5rem;
}
.range-button {
color: $shadow;
background: $background;
display: inline-block;
padding: .5rem 1rem;
border: none;
box-shadow: 0 0 2px $shadow-weak;
font-size: .8rem;
font-weight: bold;
&:hover {
color: $text;
cursor: pointer;
}
}
.range-input {
display: none;
&:checked + .range-button {
color: $primary;
}
}
@media(max-width: $breakpoint) {
.filters-container {
display: none;
}
.filters-compact {
display: inline-block;
}
}
</style>

View File

@@ -1,141 +0,0 @@
<template>
<div :class="{ compact }">
<ul class="filters">
<li class="filter">
<label
class="toggle"
:class="{ active: !localFilter.includes('lesbian') }"
>
<input
v-model="localFilter"
value="lesbian"
type="checkbox"
class="check"
@change="$emit('set-filter', localFilter)"
>lesbian
</label>
</li>
<li class="filter">
<label
class="toggle"
:class="{ active: !localFilter.includes('gay') }"
>
<input
v-model="localFilter"
value="gay"
type="checkbox"
class="check"
@change="$emit('set-filter', localFilter)"
>gay
</label>
</li>
<li class="filter">
<label
class="toggle"
:class="{ active: !localFilter.includes('transsexual') }"
>
<input
v-model="localFilter"
value="transsexual"
type="checkbox"
class="check"
@change="$emit('set-filter', localFilter)"
>trans
</label>
</li>
</ul>
<ul class="filters">
<li class="filter">
<label
class="toggle"
:class="{ active: !localFilter.includes('anal') }"
>
<input
v-model="localFilter"
value="anal"
type="checkbox"
class="check"
@change="$emit('set-filter', localFilter)"
>anal
</label>
</li>
</ul>
</div>
</template>
<script>
export default {
props: {
filter: {
type: Array,
default: () => [],
},
compact: {
type: Boolean,
default: false,
},
},
data() {
return {
localFilter: this.filter,
};
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
.filters {
display: inline-block;
list-style: none;
padding: .5rem;
margin: 0;
&:not(:last-child) {
border-right: solid 1px $shadow-hint;
}
}
.filter {
display: inline-block;
}
.compact {
.filters {
padding: 0;
border: none;
}
.filter {
margin: 0 0 1.5rem 0;
}
}
.toggle {
color: $shadow-weak;
box-sizing: border-box;
padding: .5rem;
margin: 0 .25rem;
border: solid 1px transparent;
font-size: .9rem;
font-weight: bold;
cursor: pointer;
.check {
display: none;
}
&:hover {
color: $shadow;
}
&.active {
color: $primary;
box-shadow: 0 0 2px $shadow-weak;
}
}
</style>

View File

@@ -1,143 +1,441 @@
<template>
<header class="header">
<router-link
to="/"
class="logo-link"
><h1 class="logo"><Icon icon="logo" /></h1></router-link>
<header class="header">
<div class="header-nav">
<router-link
to="/"
class="logo-link"
><h1 class="header-logo">
<div
class="logo"
v-html="logo"
/>
</h1></router-link>
<nav class="nav">
<ul class="nolist">
<li class="nav-item">
<router-link
to="/actors"
class="nav-link"
:class="{ active: active === 'actors' }"
>
<Icon icon="stars" /><span class="nav-label">Actors</span>
</router-link>
</li>
<nav class="nav">
<ul class="nav-list nolist">
<li class="nav-item">
<router-link
v-slot="{ href, isActive, navigate }"
:to="{ name: 'actors', params: { pageNumber: 1 } }"
custom
>
<a
class="nav-link"
:href="href"
:class="{ active: isActive }"
@click="navigate"
>Actors</a>
</router-link>
</li>
<li class="nav-item">
<router-link
to="/networks"
class="nav-link"
:class="{ active: active === 'networks' }"
>
<Icon icon="earth2" /><span class="nav-label">Networks</span>
</router-link>
</li>
<li class="nav-item">
<router-link
v-slot="{ href, isActive, navigate }"
:to="{ name: 'channels' }"
custom
>
<a
class="nav-link"
:href="href"
:class="{ active: isActive }"
@click="navigate"
>Channels</a>
</router-link>
</li>
<li class="nav-item">
<router-link
to="/tags"
class="nav-link"
:class="{ active: active === 'tags' }"
>
<Icon icon="price-tags" /><span class="nav-label">Tags</span>
</router-link>
</li>
</ul>
</nav>
</header>
<li class="nav-item">
<router-link
v-slot="{ href, isActive, navigate }"
:to="{ name: 'tags' }"
custom
>
<a
class="nav-link"
:href="href"
:class="{ active: isActive }"
@click="navigate"
>Tags</a>
</router-link>
</li>
<li class="nav-item">
<router-link
v-slot="{ href, isActive, navigate }"
:to="{ name: 'movies', params: { range: 'latest', pageNumber: 1 } }"
custom
>
<a
class="nav-link"
:href="href"
:class="{ active: isActive }"
@click="navigate"
>Movies</a>
</router-link>
</li>
</ul>
</nav>
</div>
<div class="header-section">
<div
class="sidebar-toggle noselect"
@click.stop="$emit('toggleSidebar')"
><Icon icon="menu" /></div>
<Tooltip v-if="me">
<div
class="header-button header-notifications"
:class="{ unseen: unseenNotificationsCount > 0 }"
>
<Icon icon="bell2" />
<span
v-if="unseenNotificationsCount > 0"
class="notifications-count"
>{{ unseenNotificationsCount }}</span>
</div>
<template v-slot:tooltip>
<Notifications
:notifications="notifications"
:unseen-count="unseenNotificationsCount"
@check="fetchNotifications"
@add-alert="showAddAlert = true"
/>
</template>
</Tooltip>
<Tooltip>
<div class="header-button header-account">
<div class="account">
<Icon
icon="user3-long"
class="avatar"
/>
</div>
</div>
<template v-slot:tooltip>
<Menu @show-filters="state => $emit('showFilters', state)" />
</template>
</Tooltip>
<Tooltip
class="search-compact"
:open="searching"
@open="searching = true"
@close="searching = false"
>
<button
type="button"
class="search-button"
><Icon
icon="search"
/></button>
<template v-slot:tooltip>
<Search
:searching="searching"
class="compact"
@search="searching = false"
@click.stop
/>
</template>
</Tooltip>
<Search class="search-full" />
</div>
<AddAlert
v-if="showAddAlert"
@close="showAddAlert = false"
>Alert</AddAlert>
</header>
</template>
<script>
function active() {
return this.$route.name;
import Menu from './menu.vue';
import Notifications from './notifications.vue';
import AddAlert from '../alerts/add.vue';
import Search from './search.vue';
import logo from '../../img/logo.svg';
function me() {
return this.$store.state.auth.user;
}
async function fetchNotifications() {
const { notifications, unseenCount } = await this.$store.dispatch('fetchNotifications');
this.notifications = notifications;
this.unseenNotificationsCount = unseenCount;
}
export default {
computed: {
active,
},
components: {
AddAlert,
Menu,
Notifications,
Search,
},
emits: ['toggleSidebar', 'showFilters'],
data() {
return {
logo,
searching: false,
showFilters: false,
showAddAlert: false,
notifications: [],
unseenNotificationsCount: 0,
};
},
computed: {
me,
},
watch: {
me: fetchNotifications,
},
mounted: fetchNotifications,
methods: {
fetchNotifications,
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
@import 'breakpoints';
.header {
height: 3rem;
display: flex;
align-items: center;
background: $background;
color: $primary;
border-bottom: solid 1px $shadow-hint;
z-index: 2;
justify-content: space-between;
background: var(--background);
color: var(--primary);
box-shadow: 0 0 3px var(--darken-weak);
font-size: 0;
}
.header-nav {
display: flex;
align-items: center;
height: 100%;
overflow: hidden;
}
.header-section {
height: 100%;
align-items: center;
display: flex;
flex-direction: row;
}
.sidebar-toggle {
display: none;
align-items: center;
height: 100%;
.icon {
display: inline-block;
fill: var(--shadow-modest);
padding: 0 1rem;
width: 1.5rem;
height: 100%;
}
&:hover {
cursor: pointer;
.icon {
fill: var(--primary);
}
}
}
.logo-link {
color: inherit;
height: 100%;
display: inline-block;
text-decoration: none;
margin: -.25rem 1rem 0 0;
}
.header-logo {
height: 100%;
display: flex;
align-items: center;
padding: 0 0 0 1rem;
fill: var(--primary);
}
.logo {
display: inline-block;
padding: .5rem 1rem;
margin: 0 1rem 0 0;
font-size: 2rem;
.icon {
width: 6rem;
height: 1.5rem;
}
width: 6rem;
display: flex;
}
.nav {
.nav,
.nav-list {
display: inline-block;
height: 100%;
}
.nav-item {
height: 100%;
}
.nav-link {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem 1rem calc(1rem - 5px) 1rem;
border-bottom: solid 5px transparent;
color: $shadow;
padding: 0 1rem;
color: var(--shadow);
text-decoration: none;
font-size: .9rem;
font-weight: bold;
.icon {
fill: $shadow;
margin: 0 .5rem 0 0;
}
cursor: pointer;
&.active {
color: $primary;
border-bottom: solid 5px $primary;
color: var(--primary);
.icon {
fill: $primary;
fill: var(--primary);
}
}
&:hover:not(.active) {
color: $primary;
color: var(--primary);
.icon {
fill: $primary;
fill: var(--primary);
}
}
}
@media(max-width: $breakpoint0) {
.nav-label {
.header-button {
padding: 1rem .75rem;
.icon {
fill: var(--shadow);
}
&:hover {
cursor: pointer;
.account {
border-color: var(--primary);
}
.icon {
fill: var(--primary);
}
}
}
.header-account {
padding: 1rem 1.25rem 1rem .75rem;
}
.header-notifications {
position: relative;
padding: 1rem .75rem;
&.unseen .icon {
fill: var(--primary);
}
}
.notifications-count {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
position: absolute;
bottom: .3rem;
left: 0;
color: var(--primary);
font-size: .6rem;
font-weight: bold;
}
.account {
width: 1.25rem;
height: 1.25rem;
display: flex;
justify-content: center;
border: solid 2px var(--shadow);
border-radius: 1.5rem;
overflow: hidden;
.avatar {
width: 1rem;
height: 1rem;
margin: .3rem 0 0 0;
fill: var(--shadow);
}
}
.search-compact {
display: none;
height: 100%;
}
.search-button {
height: 100%;
padding: .25rem 1rem 0 1rem;
background: none;
border: none;
outline: none;
.icon {
fill: var(--shadow);
}
&:hover {
cursor: pointer;
.icon {
fill: var(--shadow-strong);
}
}
}
@media(max-width: $breakpoint-kilo) {
.search-full {
display: none;
}
.nav .nolist {
.search-compact {
display: flex;
}
.nav,
.nav-item {
flex-grow: 1;
}
.nav-link {
.header-account {
padding: 1rem .5rem 1rem 1rem;
}
}
@media(max-width: $breakpoint-small) {
.sidebar-toggle {
display: flex;
}
.logo-link {
margin: -.25rem 1.25rem 0 0;
}
.nav-link {
padding: 0.05rem .75rem 0 .75rem;
}
.search-compact {
display: none;
}
.header-account,
.header-notifications {
display: none;
}
}
</style>

View File

@@ -0,0 +1,190 @@
<template>
<div class="menu">
<ul class="menu-items noselect">
<RouterLink
v-if="login && me"
:to="{ name: 'user', params: { username: me.username } }"
class="menu-username"
>{{ me.username }}</RouterLink>
<RouterLink
v-if="me && favorites"
:to="{ name: 'stash', params: { stashId: favorites.id, range: 'scenes', pageNumber: 1 } }"
class="menu-item"
><Icon icon="heart7" />Favorites</RouterLink>
<RouterLink
v-else-if="login"
:to="{ name: 'login', query: { ref: $route.path } }"
class="menu-item"
@click.stop
>
<Icon icon="enter2" />Log in
</RouterLink>
<li
v-show="!sfw"
class="menu-item"
@click.stop="setSfw(true)"
>
<Icon
icon="flower"
class="toggle noselect"
/>Safe mode
</li>
<li
v-show="sfw"
class="menu-item"
@click.stop="setSfw(false)"
>
<Icon
icon="fire"
class="toggle noselect"
/>Filth mode
</li>
<li
v-show="theme === 'light'"
class="menu-item"
@click.stop="setTheme('dark')"
>
<Icon
icon="moon"
class="toggle noselect"
/>Dark theme
</li>
<li
v-show="theme === 'dark'"
class="menu-item"
@click.stop="setTheme('light')"
>
<Icon
icon="sun"
class="toggle noselect"
/>Light theme
</li>
<li
class="menu-item"
@click="$emit('showFilters', true)"
>
<Icon icon="filter" />Filters
</li>
<li
v-if="login && me"
class="menu-item"
@click.stop="$store.dispatch('logout')"
>
<Icon icon="exit2" />Log out
</li>
</ul>
</div>
</template>
<script>
import { mapState } from 'vuex';
function sfw(state) {
return state.ui.sfw;
}
function theme(state) {
return state.ui.theme;
}
function login(state) {
return state.auth.login;
}
function signup(state) {
return state.auth.signup;
}
function favorites() {
return this.me?.stashes.find(stash => stash.primary);
}
function me(state) {
return state.auth.user;
}
function setTheme(newTheme) {
this.$store.dispatch('setTheme', newTheme);
}
function setSfw(enabled) {
this.$store.dispatch('setSfw', enabled);
}
export default {
computed: {
...mapState({
login,
signup,
sfw,
theme,
me,
}),
favorites,
},
emits: ['showFilters'],
methods: {
setSfw,
setTheme,
},
};
</script>
<style lang="scss" scoped>
@import 'breakpoints';
.menu-items {
list-style: none;
padding: 0;
margin: 0;
}
.menu-item {
display: flex;
padding: .75rem 1rem .75rem .75rem;
color: var(--text);
text-decoration: none;
.icon {
fill: var(--shadow);
margin: 0 1rem 0 0;
}
&.disabled {
color: var(--shadow-weak);
cursor: default;
.icon {
fill: var(--shadow-weak);
}
}
&:hover:not(.disabled) {
cursor: pointer;
color: var(--primary);
.icon {
fill: var(--primary);
}
}
}
.menu-username {
display: block;
font-weight: bold;
color: var(--shadow-strong);
font-size: .9rem;
padding: .75rem 1rem;
border-bottom: solid 1px var(--shadow-hint);
text-align: center;
text-decoration: none;
}
</style>

View File

@@ -0,0 +1,341 @@
<template>
<div class="notifications">
<div class="notifications-header">
<h4 class="notifications-title">Notifications</h4>
<div class="notifications-actions">
<Icon
v-if="unseenCount > 0"
v-tooltip="'Mark all as seen'"
icon="checkmark"
@click="checkNotifications"
/>
<Icon
v-tooltip="'Add alert'"
icon="plus3"
@click="$emit('addAlert')"
/>
</div>
</div>
<div class="notifications-body">
<div
v-if="notifications.length === 0"
class="notifications-empty"
>No notifications</div>
<ul
v-else
class="nolist"
>
<li
v-for="notification in notifications"
:key="`notification-${notification.id}`"
:class="{ unseen: !notification.seen }"
class="notification"
@click="checkNotification(notification.id, true)"
>
<router-link
:to="`/scene/${notification.scene.id}/${notification.scene.slug}`"
class="notification-link"
>
<img
:src="getPath(notification.scene.poster, 'thumbnail')"
class="poster"
>
<div class="notification-body">
<div class="notification-row notification-title">
<img
v-if="notification.scene.entity.type === 'network' || notification.scene.entity.independent"
v-tooltip="notification.scene.entity.name"
:src="`/img/logos/${notification.scene.entity.slug}/favicon_${theme === 'dark' ? 'light' : 'dark'}.png`"
class="notification-favicon"
>
<img
v-else
v-tooltip="notification.scene.entity.name"
:src="`/img/logos/${notification.scene.entity.parent.slug}/favicon_${theme === 'dark' ? 'light' : 'dark'}.png`"
class="notification-favicon"
>
New&nbsp;<ul
v-if="notification.alert?.tags.length > 0"
class="nolist notification-tags"
>
<li
v-for="tag in notification.alert.tags"
:key="`notification-tag-${tag.slug}`"
class="notification-tag"
>{{ tag.name }}</li>&nbsp;
</ul>scene
</div>
<div class="notification-row notification-details">
<span class="notification-date">{{ formatDate(notification.scene.date, 'MMM D') }}</span>
<ul
v-if="notification.scene.actors.length > 0"
class="nolist notification-actors"
>
<li
v-for="actor in notification.scene.actors"
:key="`notification-actor-${actor.slug}`"
class="notification-actor"
>{{ actor.name }}</li>
</ul>
</div>
</div>
<Icon
v-if="!notification.seen"
v-tooltip="'Mark as seen'"
icon="checkmark"
class="notification-check"
@click.prevent.stop="checkNotification(notification.id)"
/>
<Icon
v-if="notification.alert"
v-tooltip="`You set an alert for <strong>${notification.alert.tags.map(tag => tag.name).join(', ') || 'all'}</strong> scenes with <strong>${notification.alert.actors.map(actor => actor.name).join(', ') || 'any actor'}</strong> for <strong>${notification.alert.entity?.name || 'any channel'}</strong>`"
icon="question5"
@click.prevent.stop
/>
</router-link>
</li>
</ul>
</div>
<div @click="events.emit('blur')">
<router-link
to="/notifications"
class="notification-link notification-more"
>See all</router-link>
</div>
</div>
</template>
<script>
async function checkNotifications() {
await this.$store.dispatch('checkNotifications');
this.$emit('check');
}
async function checkNotification(notificationId, blur) {
await this.$store.dispatch('checkNotification', notificationId);
this.$emit('check');
if (blur) {
this.events.emit('blur');
}
}
export default {
props: {
notifications: {
type: Array,
default: () => [],
},
unseenCount: {
type: Number,
default: 0,
},
},
data() {
return {
showAddAlert: false,
};
},
emits: ['addAlert'],
methods: {
checkNotifications,
checkNotification,
},
};
</script>
<style lang="scss" scoped>
.notifications {
width: 30rem;
max-height: calc(100vh - 5rem);
display: flex;
flex-direction: column;
}
.notifications-header {
display: flex;
justify-content: space-between;
.icon {
padding: .5rem;
fill: var(--shadow);
&:first-child {
padding: .5rem .5rem .5rem 1.5rem;
}
&:last-child {
padding: .5rem 1.5rem .5rem .5rem;
}
&:hover {
fill: var(--primary);
cursor: pointer;
}
}
}
.notifications-title {
display: inline-block;
padding: .5rem 1rem;
margin: 0;
color: var(--shadow);
font-size: 1rem;
font-weight: bold;
}
.notifications-body {
flex-grow: 1;
overflow-y: auto;
box-shadow: 0 0 3px var(--shadow-weak);
}
.notifications-empty {
padding: .5rem 1rem;
color: var(--shadow);
}
.notification {
display: block;
border-right: solid .5rem var(--shadow-touch);
color: var(--text);
&.unseen {
border-right: solid .5rem var(--primary);
}
.icon {
padding: 1.3rem .5rem;
fill: var(--shadow-weak);
&.notification-check {
padding: 1.3rem .5rem 1.3rem 1rem;
}
&:last-child {
padding: 1.3rem 1rem 1.3rem .5rem;
}
&:hover {
fill: var(--primary);
}
}
&:not(:last-child) {
border-bottom: solid 1px var(--shadow-hint);
margin: 0 0 -1px 0;
}
&:hover {
background: var(--shadow-touch);
&:not(.unseen) {
border-right: solid .5rem var(--shadow-weak);
}
}
}
.notification-link {
display: flex;
align-items: stretch;
color: inherit;
text-decoration: none;
}
.notification-body {
flex-grow: 1;
padding: .4rem 0 0 0;
overflow: hidden;
}
.notification-row {
display: flex;
overflow: hidden;
}
.notification-title {
margin: .15rem .5rem .3rem .5rem;
}
.notification-favicon {
width: 1rem;
height: 1rem;
margin: 0 .5rem 0 0;
}
.notification-tags {
white-space: nowrap;
}
.notification-actors {
padding: 0 .5rem;
height: 1.25rem;
display: inline-block;
overflow: hidden;
}
.notification-date {
width: 3rem;
flex-shrink: 0;
padding: .25rem .25rem .35rem .25rem;
border-right: solid 1px var(--shadow-hint);
border-top: solid 1px var(--shadow-hint);
color: var(--shadow-strong);
font-size: .8rem;
text-align: center;
}
.notification-actor,
.notification-tag {
white-space: nowrap;
&:not(:last-child)::after {
content: ',';
padding: 0 .1rem 0 0;
}
}
.notification-actor {
padding: .25rem .15rem .35rem 0;
color: var(--shadow-strong);
font-size: .9rem;
}
.notification-tag {
font-weight: bold;
}
.notification-more {
display: block;
padding: .5rem 1rem;
color: var(--shadow);
text-align: center;
font-size: .9rem;
font-weight: bold;
&:hover {
color: var(--primary);
}
}
.poster {
width: 6rem;
height: 3.6rem;
object-fit: cover;
object-position: center;
}
</style>

View File

@@ -0,0 +1,154 @@
<template>
<form
class="search"
@submit.prevent="search"
>
<input
ref="search"
v-model="query"
type="search"
class="search-input"
placeholder="Search..."
>
<button
type="submit"
class="search-button"
><Icon
icon="search"
/></button>
</form>
</template>
<script>
async function search() {
if (this.query) {
this.$router.push({ name: 'search', query: { q: this.query } });
this.$emit('search');
}
}
function searching(to) {
if (to) {
this.$refs.search.focus();
}
}
function route(to) {
if (to.name !== 'search') {
this.query = null;
}
}
export default {
props: {
searching: {
type: Boolean,
default: false,
},
},
data() {
return {
query: this.$route.query ? this.$route.query.q : null,
};
},
emits: ['search'],
watch: {
$route: route,
searching,
},
methods: {
search,
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
.search {
height: 100%;
max-width: 20rem;
display: flex;
flex-grow: 1;
align-items: center;
justify-content: flex-end;
border-left: solid 1px var(--shadow-hint);
margin: 0 .25rem 0 0;
&.compact {
padding: 0;
border: none;
.search-input {
padding: .75rem .5rem .75rem .75rem;
}
.search-button {
padding: 1rem 1rem .75rem .25rem;
margin: 0;
}
}
}
.search-input {
height: 100%;
width: 100%;
padding: .5rem 0 .5rem .75rem;
border: none;
color: var(--text);
background: var(--background);
outline: none;
font-size: 1rem;
outline: none;
&::placeholder {
color: var(--shadow);
}
&::-webkit-search-cancel-button {
-webkit-appearance: none;
padding: .5rem;
position: relative;
right: 0;
color: var(--text);
background: url('/img/cancel-circle2.svg');
opacity: .25;
&:hover {
opacity: .5;
cursor: pointer;
}
}
&:focus {
&::placeholder {
color: var(--shadow-weak);
}
& + .search-button:not(:hover) .icon {
fill: var(--shadow);
}
}
}
.search-button {
height: 100%;
padding: 0 1.25rem 0 1rem;
background: none;
border: none;
margin: .3rem 0 0 0;
outline: none;
.icon {
fill: var(--shadow-weak);
}
&:hover {
cursor: pointer;
.icon {
fill: var(--primary);
}
}
}
</style>

View File

@@ -1,44 +1,94 @@
<template>
<div class="content">
<FilterBar :fetch-releases="fetchReleases" />
<div class="home">
<div class="content-inner">
<FilterBar
ref="filter"
:fetch-releases="fetchReleases"
:is-home="true"
:items-total="totalCount"
:items-per-page="limit"
:content="$refs.content"
:show-total="false"
/>
<div class="content-inner">
<Releases
:releases="releases"
/>
</div>
</div>
<Releases
:releases="releases"
:done="done"
/>
<Pagination
v-if="totalCount > 0"
:items-total="totalCount"
:items-per-page="limit"
class="pagination-bottom"
/>
</div>
<Footer />
</div>
</template>
<script>
import FilterBar from '../header/filter-bar.vue';
import FilterBar from '../filters/filter-bar.vue';
import Releases from '../releases/releases.vue';
import Pagination from '../pagination/pagination.vue';
async function fetchReleases() {
this.releases = await this.$store.dispatch('fetchReleases', { limit: 100 });
async function fetchReleases(scroll = true) {
this.done = false;
const { releases, totalCount } = await this.$store.dispatch('fetchReleases', {
limit: this.limit,
range: this.$route.params.range,
pageNumber: Number(this.$route.params.pageNumber) || 1,
});
this.totalCount = totalCount;
this.releases = releases;
this.done = true;
if (scroll) {
this.$refs.filter?.$el.scrollIntoView();
}
}
async function mounted() {
this.pageTitle = '';
this.pageTitle = '';
await this.fetchReleases();
await this.fetchReleases();
}
export default {
components: {
FilterBar,
Releases,
},
data() {
return {
releases: [],
networks: [],
pageTitle: null,
};
},
mounted,
methods: {
fetchReleases,
},
components: {
FilterBar,
Releases,
Pagination,
},
data() {
return {
releases: [],
networks: [],
pageTitle: null,
limit: 30,
totalCount: 0,
from: null,
done: false,
};
},
watch: {
$route: fetchReleases,
'$store.state.ui.tagFilter': fetchReleases,
},
mounted,
methods: {
fetchReleases,
},
};
</script>
<style lang="scss" scoped>
.home {
display: flex;
flex-direction: column;
flex-grow: 1;
}
</style>

View File

@@ -1,36 +1,35 @@
<template>
<div
:title="title"
:class="{ active }"
class="icon"
v-html="svg"
/>
<div
:class="{ active }"
class="icon"
v-html="svg"
/>
</template>
<script>
export default {
props: {
icon: {
type: String,
default: null,
},
title: {
type: String,
default: null,
},
active: {
type: Boolean,
default: false,
},
},
data() {
return {
svg: null,
};
},
beforeMount() {
this.svg = require(`../../img/${this.icon}.svg`).default;
},
props: {
icon: {
type: String,
default: null,
},
title: {
type: String,
default: null,
},
active: {
type: Boolean,
default: false,
},
},
data() {
return {
svg: null,
};
},
beforeMount() {
this.svg = require(`../../img/icons/${this.icon}.svg`).default; // eslint-disable-line global-require, import/no-dynamic-require
},
};
</script>
@@ -38,7 +37,7 @@ export default {
@import '../../css/theme';
.icon {
fill: $text;
fill: var(--text);
display: inline-block;
flex-shrink: 0;
width: 1rem;
@@ -50,10 +49,10 @@ export default {
}
&.active {
fill: $shadow;
fill: var(--shadow);
&:hover {
fill: $text;
fill: var(--text);
cursor: pointer;
}
}

View File

@@ -0,0 +1,90 @@
<template>
<div class="load-container">
<div class="load-ellipsis">
<div />
<div />
<div />
<div />
</div>
</div>
</template>
<style lang="scss" scoped>
.load-container {
display: inline-flex;
position: relative;
padding: 1rem;
}
.load-ellipsis {
display: inline-block;
position: relative;
width: 5rem;
height: .75rem;
}
.load-ellipsis div {
position: absolute;
top: 0;
width: .75rem;
height: .75rem;
border-radius: 50%;
background: var(--primary);
}
.load-ellipsis div:nth-child(1) {
left: .5rem;
animation: load-ellipsis1 0.5s infinite linear;
}
.load-ellipsis div:nth-child(2) {
left: .5rem;
animation: load-ellipsis2 0.5s infinite linear;
}
.load-ellipsis div:nth-child(3) {
left: 2rem;
animation: load-ellipsis3 0.5s infinite linear;
}
.load-ellipsis div:nth-child(4) {
left: 3.5rem;
animation: load-ellipsis4 0.5s infinite linear;
}
@keyframes load-ellipsis1 {
0% {
transform: scale(0);
}
100% {
transform: scale(0.5);
}
}
@keyframes load-ellipsis2 {
0% {
transform: translate(0, 0) scale(0.5);
}
100% {
transform: translate(1.5rem, 0) scale(1);
}
}
@keyframes load-ellipsis3 {
0% {
transform: translate(0, 0) scale(1);
}
100% {
transform: translate(1.5rem, 0) scale(0.5);
}
}
@keyframes load-ellipsis4 {
0% {
transform: scale(0.5);
}
100% {
transform: scale(0);
}
}
</style>

View File

@@ -1,282 +0,0 @@
<template>
<div
v-if="network"
class="content"
>
<FilterBar :fetch-releases="fetchNetwork" />
<div
class="network"
:class="{ nosites: sites.length === 0 }"
>
<div
v-show="sites.length > 0"
class="sidebar"
:class="{ expanded }"
>
<a
v-tooltip.bottom="`Go to ${network.url}`"
:href="network.url"
target="_blank"
rel="noopener noreferrer"
class="title"
>
<img
:src="`/img/logos/${network.slug}/network.png`"
class="logo"
>
</a>
<p
v-if="network.description"
class="description"
>{{ network.description }}</p>
<Sites
v-if="sites.length"
:sites="sites"
:class="{ expanded }"
/>
</div>
<template v-if="sites.length > 0">
<span
v-show="!expanded"
class="expand expand-sidebar noselect"
@click="expanded = true"
><Icon icon="arrow-right3" /></span>
<span
v-show="expanded"
class="expand expand-sidebar noselect"
@click="expanded = false"
><Icon icon="arrow-left3" /></span>
</template>
<div
class="header"
:class="{ hideable: sites.length > 0 }"
>
<a
v-tooltip.bottom="`Go to ${network.url}`"
:href="network.url"
target="_blank"
rel="noopener noreferrer"
class="title"
>
<img
:src="`/img/logos/${network.slug}/network.png`"
class="logo"
>
</a>
</div>
<div class="content-inner">
<template v-if="sites.length > 0">
<span
v-show="expanded"
class="expand collapse-header noselect"
@click="expanded = false"
><Icon icon="arrow-up3" /></span>
<Sites
:sites="sites"
:class="{ expanded }"
class="compact"
/>
<span
v-show="!expanded"
class="expand expand-header noselect"
@click="expanded = true"
><Icon icon="arrow-down3" /></span>
<span
v-show="expanded"
class="expand expand-header noselect"
@click="expanded = false"
><Icon icon="arrow-up3" /></span>
</template>
<Releases :releases="releases" />
</div>
</div>
</div>
</template>
<script>
import FilterBar from '../header/filter-bar.vue';
import Releases from '../releases/releases.vue';
import Sites from '../sites/sites.vue';
async function fetchNetwork() {
this.network = await this.$store.dispatch('fetchNetworks', this.$route.params.networkSlug);
if (this.network.studios) {
this.studios = this.network.studios.map(studio => ({
...studio,
network: this.network,
}));
}
this.sites = this.network.sites
.filter(site => !site.independent)
// .concat(this.studios)
.sort(({ name: nameA }, { name: nameB }) => nameA.localeCompare(nameB));
this.releases = this.network.releases;
}
async function mounted() {
await this.fetchNetwork();
this.pageTitle = this.network.name;
}
export default {
components: {
FilterBar,
Releases,
Sites,
},
data() {
return {
network: null,
sites: [],
studios: [],
releases: [],
pageTitle: null,
expanded: false,
};
},
mounted,
methods: {
fetchNetwork,
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
.network {
display: flex;
flex-direction: row;
flex-grow: 1;
justify-content: stretch;
overflow-y: auto;
&.nosites {
flex-direction: column;
}
}
.content-inner {
padding: 0;
}
.releases {
padding: 1rem 1rem 1rem .5rem;
}
.sidebar {
background: $profile;
height: 100%;
width: 18rem;
display: flex;
flex-direction: column;
flex-shrink: 0;
color: $text-contrast;
overflow: hidden;
.title {
display: flex;
justify-content: center;
border-bottom: solid 1px $highlight-hint;
}
&.expanded {
width: calc(100% - 25rem);
.logo {
max-width: 18rem;
}
}
}
.logo {
width: 100%;
max-height: 8rem;
display: flex;
justify-content: center;
object-fit: contain;
box-sizing: border-box;
padding: 1rem;
filter: $logo-highlight;
}
.header {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
flex-shrink: 0;
border-bottom: solid 1px $shadow-hint;
background: $profile;
&.hideable {
display: none;
}
.logo {
max-width: 20rem;
max-height: 3rem;
padding: .5rem;
}
}
.sites.compact {
display: none;
background: $profile;
grid-row: 1;
}
.collapse-header {
display: none;
}
@media(max-width: $breakpoint3) {
.header,
.header.hideable {
display: flex;
}
.sites.compact {
display: flex;
&.expanded {
display: grid;
}
}
.expand-header,
.collapse-header {
display: flex;
}
.expand-sidebar,
.collapse-sidebar {
display: none;
}
.network {
flex-direction: column;
}
.sidebar {
display: none;
height: auto;
width: 100%;
overflow: hidden;
}
}
</style>

View File

@@ -1,75 +1,202 @@
<template>
<div class="networks">
<input
:placeholder="`Find ${siteCount} sites in ${networks.length} networks`"
class="search"
>
<div class="networks">
<div class="content-inner">
<SearchBar
:placeholder="`Search ${channelCount} channels in ${entities.length} networks`"
:eager="true"
/>
<div class="network-tiles">
<Network
v-for="network in networks"
:key="`network-tile-${network.slug}`"
:network="network"
/>
</div>
</div>
<span
v-if="done && entities.length === 0"
class="empty"
>No results for "{{ $route.query.query }}"</span>
<template v-else>
<h2 class="heading">Popular</h2>
<div
class="entity-tiles"
>
<Entity
v-for="entity in popularEntities"
:key="entity.parent ? `entity-tile-${entity.parent.slug}-${entity.slug}` : `entity-tile-${entity.slug}`"
:entity="entity"
/>
</div>
<h2 class="heading">All networks</h2>
<div
class="entity-tiles"
>
<Entity
v-for="entity in entities"
:key="entity.parent ? `entity-tile-${entity.parent.slug}-${entity.slug}` : `entity-tile-${entity.slug}`"
:entity="entity"
/>
</div>
</template>
</div>
<Footer />
</div>
</template>
<script>
import Network from '../tile/network.vue';
import Entity from '../entities/tile.vue';
import SearchBar from '../search/bar.vue';
async function mounted() {
this.networks = await this.$store.dispatch('fetchNetworks');
async function fetchEntities() {
if (this.$route.query.query) {
await this.searchEntities();
return;
}
this.done = false;
this.entities = await this.$store.dispatch('fetchEntities', {
type: 'network',
entitySlugs: [],
});
this.done = true;
}
function siteCount() {
return this.networks.map(network => network.sites).flat().length;
async function searchEntities() {
this.done = false;
this.entities = await this.$store.dispatch('searchEntities', {
query: this.$route.query.query,
limit: 20,
});
this.done = true;
}
function popularEntities() {
const entitiesBySlug = Object.fromEntries(this.entities.map((entity) => [entity.slug, entity]));
return [
'21sextury',
'amateurallure',
'analvids',
'bamvisions',
'bang',
'bangbros',
'blowpass',
'brazzers',
'burningangel',
'digitalplayground',
'dogfartnetwork',
'dorcel',
'elegantangel',
'evilangel',
'fakehub',
'girlsway',
'hookuphotshot',
'hussiepass',
'insex',
'julesjordan',
'kellymadison',
'kink',
'mofos',
'naughtyamerica',
'newsensations',
'pervcity',
'pornpros',
'private',
'realitykings',
'twistys',
'vixen',
'xempire',
].map((slug) => entitiesBySlug[slug]).filter(Boolean);
}
async function mounted() {
this.pageTitle = 'Channels';
await this.fetchEntities();
}
function channelCount() {
return this.entities.reduce((acc, entity) => acc + entity.childrenTotal, 0);
}
export default {
components: {
Network,
},
data() {
return {
networks: [],
};
},
computed: {
siteCount,
},
mounted,
components: {
Entity,
SearchBar,
},
data() {
return {
done: false,
pageTitle: null,
entities: [],
};
},
computed: {
channelCount,
popularEntities,
},
watch: {
$route: fetchEntities,
},
mounted,
methods: {
fetchEntities,
searchEntities,
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
.search {
width: 40rem;
max-width: 100%;
box-sizing: border-box;
padding: 1rem;
border: none;
box-shadow: 0 0 3px $shadow-weak;
margin: 1rem;
font-size: 1rem;
outline: none;
.networks {
display: flex;
flex-direction: column;
flex-grow: 1;
}
&:focus {
box-shadow: 0 0 3px $primary;
.content-inner {
padding: 0 1rem;
}
.search {
margin: 1rem 0 0 0;
}
.entity-tiles {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
grid-gap: .5rem;
padding: 1rem 0;
.tile {
height: 6rem;
}
}
.empty {
display: block;
margin: 1rem 0;
color: var(--shadow);
font-size: 1.25rem;
font-weight: bold;
}
.heading {
margin: 1rem 0 0 0;
}
@media(max-width: $breakpoint2) {
.entity-tiles {
grid-gap: .5rem;
}
}
.network-tiles {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
grid-gap: 1rem;
padding: 1rem;
}
@media(max-width: $breakpoint) {
.networks {
@media(max-width: $breakpoint0) {
.entity-tiles {
grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
}
}

View File

@@ -0,0 +1,76 @@
<template>
<div
ref="page"
class="notifications-container"
>
<h1 class="heading">Notifications</h1>
<ul class="notifications nolist">
<li
v-for="notification in notifications"
:key="notification.id"
>
<SceneTile :release="notification.scene" />
</li>
</ul>
<Pagination
:items-total="totalCount"
:items-per-page="10"
/>
</div>
</template>
<script>
import Pagination from '../pagination/pagination.vue';
import SceneTile from '../releases/scene-tile.vue';
async function fetchNotifications() {
const { notifications, totalCount, unseenCount } = await this.$store.dispatch('fetchNotifications', {
page: this.$route.params.pageNumber,
limit: this.limit,
});
this.notifications = notifications;
this.unseenNotificationsCount = unseenCount;
this.totalCount = totalCount;
this.$emit('scroll');
}
export default {
components: {
Pagination,
SceneTile,
},
data() {
return {
notifications: [],
limit: 10,
totalCount: 0,
unseenNotificationsCount: 0,
};
},
emits: ['scroll'],
watch: {
$route: fetchNotifications,
},
mounted: fetchNotifications,
};
</script>
<style lang="scss" scoped>
.notifications-container {
padding: 1rem;
}
.notifications {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(22rem, 1fr));
grid-gap: .5rem;
}
.pagination {
margin: 1rem 0 0 0;
}
</style>

View File

@@ -0,0 +1,197 @@
<template>
<div
v-if="itemsTotal > 0 || !hideEmpty"
class="pagination"
>
<span
v-show="pageNumber > 1"
class="cursors"
>
<router-link
class="pagination-button cursor"
:to="{ name, params: { ...params, pageNumber: 1 }, query: $route.query }"
><Icon icon="first2" /></router-link>
<router-link
class="pagination-button cursor"
:to="{ name, params: { ...params, pageNumber: pageNumber - 1 }, query: $route.query }"
><Icon icon="arrow-left" /></router-link>
</span>
<span
v-show="pageNumber === 1"
class="cursors"
>
<span class="pagination-button cursor disabled"><Icon icon="first2" /></span>
<span class="pagination-button cursor disabled"><Icon icon="arrow-left" /></span>
</span>
<span class="pages pages-before">
<router-link
v-for="pageX in pageNumber - 1"
:key="`page-${pageX}`"
:to="{ name, params: { ...params, pageNumber: pageNumber - pageX }, query: $route.query }"
class="pagination-button page"
> {{ pageNumber - pageX }} </router-link>
</span>
<router-link
:key="`page-${pageNumber}`"
:to="{ name, params: { ...params, pageNumber }, query: $route.query }"
class="pagination-button page active"
> {{ pageNumber }} </router-link>
<span class="pages pages-after">
<router-link
v-for="pageX in (pageCount - pageNumber)"
:key="`page-${pageX + pageNumber}`"
:to="{ name, params: { ...params, pageNumber: pageX + pageNumber }, query: $route.query }"
class="pagination-button page"
> {{ pageX + pageNumber }} </router-link>
</span>
<span
v-show="pageNumber < pageCount"
class="cursors"
>
<router-link
class="pagination-button cursor"
:to="{ name, params: { ...params, pageNumber: pageNumber + 1 }, query: $route.query }"
><Icon icon="arrow-right" /></router-link>
<router-link
class="pagination-button cursor"
:to="{ name, params: { ...params, pageNumber: pageCount }, query: $route.query }"
><Icon icon="last2" /></router-link>
</span>
<span
v-show="pageNumber === pageCount"
class="cursors"
>
<span class="pagination-button cursor disabled"><Icon icon="arrow-right" /></span>
<span class="pagination-button cursor disabled"><Icon icon="last2" /></span>
</span>
</div>
</template>
<script>
function pageNumber() {
return Number(this.$route.params.pageNumber) || 1;
}
function pageCount() {
const count = Math.max(Math.ceil(this.itemsTotal / this.itemsPerPage), 1);
return count;
}
export default {
props: {
itemsTotal: {
type: Number,
default: 0,
},
itemsPerPage: {
type: Number,
default: 10,
},
hideEmpty: {
type: Boolean,
default: true,
},
name: {
type: String,
default: null,
},
params: {
type: Object,
default: null,
},
},
computed: {
pageNumber,
pageCount,
},
};
</script>
<style lang="scss" scoped>
.pagination {
display: flex;
justify-content: center;
flex-shrink: 0;
overflow: hidden;
height: 3rem;
}
.pagination-top {
margin: 0 0 1rem 0;
}
.pagination-bottom {
margin: .5rem 0 1rem 0;
}
.pagination-button {
width: 2.5rem;
height: 2.5rem;
display: inline-flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
margin: .1rem 0 2rem 0;
background: var(--background);
color: var(--shadow);
font-size: 1rem;
font-weight: bold;
text-decoration: none;
box-shadow: 0 0 3px var(--shadow-hint);
.icon {
width: .8rem;
height: .8rem;
margin: 0 0 .125rem 0;
fill: var(--shadow);
}
&:hover:not(.active):not(.disabled) {
color: var(--shadow-strong);
.icon {
fill: var(--shadow-strong);
}
}
&.active {
color: var(--primary);
}
&.disabled {
color: var(--shadow-weak);
.icon {
fill: var(--shadow-weak);
}
}
}
.pages {
max-width: 10rem;
display: inline-flex;
flex-wrap: wrap;
}
.pages-before {
flex-direction: row-reverse;
}
.cursors {
flex-shrink: 0;
}
.cursors {
margin: 0 .75rem;
font-size: 0;
}
</style>

View File

@@ -1,113 +1,385 @@
<template>
<div
class="banner"
@wheel.prevent="scrollBanner"
>
<template v-if="release.covers && release.covers.length > 0">
<a
v-for="cover in release.covers"
:key="`cover-${cover.id}`"
:href="`/media/${cover.path}`"
target="_blank"
rel="noopener noreferrer"
>
<img
:src="`/media/${cover.thumbnail}`"
class="cover"
>
</a>
</template>
<div class="media-container">
<div
class="media"
:class="{ center: (release.photos?.length || 0) + (release.scenesPhotos?.length || 0) < 2, preview: !me }"
>
<div
v-if="release.trailer || release.teaser"
class="trailer-container"
>
<Player
v-if="release.trailer"
:video="release.trailer"
:poster="poster"
class="item trailer"
@play="playing = true; paused = false;"
@pause="playing = false; paused = true;"
/>
<div class="trailer">
<video
v-if="release.trailer"
:src="`/media/${release.trailer.path}`"
:poster="`/media/${(release.poster && release.poster.thumbnail)}`"
:alt="release.title"
class="item trailer-video"
controls
>Sorry, the tailer cannot be played in your browser</video>
</div>
<Player
v-else-if="release.teaser && /^video\//.test(release.teaser.mime)"
:video="release.teaser"
:poster="poster"
:alt="release.title"
:class="{ sfw: sfw && paused }"
class="item trailer"
@play="playing = true; paused = false;"
@pause="playing = false; paused = true;"
/>
<a
v-for="photo in photos"
:key="`banner-${photo.index}`"
:href="`/media/${photo.path}`"
target="_blank"
rel="noopener noreferrer"
>
<img
:src="`/media/${photo.thumbnail}`"
:alt="`Photo ${photo.index + 1}`"
class="item"
>
</a>
</div>
<img
v-else-if="release.teaser && /^image\//.test(release.teaser.mime)"
:src="getPath(release.teaser, 'thumbnail', { original: true })"
:alt="release.title"
:width="release.teaser.thumbnailWidth"
:height="release.teaser.thumbnailHeight"
loading="lazy"
class="item trailer"
>
<span
v-if="release.poster || release.teaser"
class="poster-links"
>
<a
v-if="release.teaser"
v-tooltip="'View teaser'"
:href="getPath(release.teaser)"
:class="{ playing }"
target="_blank"
rel="noopener noreferrer"
class="poster-link"
><Icon icon="film4" /></a>
<a
v-if="release.poster"
v-tooltip="'View poster'"
:href="getPath(release.poster)"
:class="{ playing }"
target="_blank"
rel="noopener noreferrer"
class="poster-link"
><Icon icon="image" /></a>
</span>
<span
v-if="sfw && !playing"
class="warning"
>
<Icon icon="warning2" />NSFW
</span>
</div>
<template v-if="release.covers?.length > 0">
<div
v-for="cover in release.covers"
:key="`cover-${cover.id}`"
class="item-container"
>
<a
:href="getPath(cover)"
target="_blank"
rel="noopener noreferrer"
>
<img
:src="getPath(cover, 'thumbnail')"
:style="{ 'background-image': getBgPath(cover, 'lazy') }"
:width="cover.thumbnailWidth"
:height="cover.thumbnailHeight"
class="item cover"
loading="lazy"
@load="$emit('load', $event)"
>
</a>
</div>
</template>
<div
v-for="photo in photos"
:key="`media-${photo.id}`"
class="item-container"
>
<a
:href="getPath(photo)"
:class="{ sfw }"
class="item-link"
target="_blank"
rel="noopener noreferrer"
>
<img
:src="getPath(photo, 'thumbnail')"
:style="{ 'background-image': `url('${getPath(photo, 'lazy')}` }"
:alt="`Photo ${photo.index + 1}`"
:width="photo.thumbnailWidth"
:height="photo.thumbnailHeight"
loading="lazy"
class="item"
@load="$emit('load', $event)"
>
<span
v-if="sfw"
class="warning"
>
<Icon icon="warning2" />NSFW
</span>
</a>
</div>
<div
v-if="!me"
class="item-container item-more"
><router-link
:to="{ name: 'login', query: { ref: $route.path } }"
class="link"
>Log in</router-link>&nbsp;for more photos, trailers and features!</div>
</div>
</div>
</template>
<script>
function photos() {
if (this.release.trailer) {
// poster will be on trailer video
return this.release.photos;
}
import Player from '../video/player.vue';
if (this.release.poster) {
return [this.release.poster].concat(this.release.photos);
}
return this.release.photos;
function sfw() {
return this.$store.state.ui.sfw;
}
function scrollBanner(event) {
event.currentTarget.scrollLeft += event.deltaY; // eslint-disable-line no-param-reassign
function me() {
return this.$store.state.auth.user;
}
function poster() {
if (this.release.poster) {
return this.getPath(this.release.poster, 'thumbnail');
}
if (this.release.covers?.length > 0) {
return this.getPath(this.release.covers[0], 'thumbnail');
}
if (this.photos?.length > 0) {
return this.getPath(this.release.photos[0], 'thumbnail');
}
return null;
}
function photos() {
const clips = this.release.clips || [];
const clipPostersById = clips.reduce((acc, clip) => ({ ...acc, [clip.poster.id]: clip.poster }), {});
const uniqueClipPosters = Array.from(new Set(clips.map((clip) => clip.poster.id) || [])).map((posterId) => clipPostersById[posterId]);
const photosWithClipPosters = (this.release.photos || []).concat(this.release.scenesPhotos || []).concat(uniqueClipPosters);
if (this.release.trailer || (this.release.teaser && this.release.teaser.mime !== 'image/gif')) {
// poster will be on trailer video
return photosWithClipPosters;
}
if (this.release.poster) {
// no trailer, add poster to photos
return [this.release.poster].concat(photosWithClipPosters);
}
// no poster available
return photosWithClipPosters;
}
export default {
props: {
release: {
type: Object,
default: null,
},
},
computed: {
photos,
},
methods: {
scrollBanner,
},
components: {
Player,
},
props: {
release: {
type: Object,
default: null,
},
expanded: {
type: Boolean,
default: false,
},
test: {
type: String,
default: null,
},
},
emits: ['load'],
data() {
return {
player: null,
playing: false,
paused: false,
};
},
computed: {
me,
photos,
poster,
sfw,
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
@import 'breakpoints';
.banner {
background: $empty;
.media {
flex-shrink: 0;
white-space: nowrap;
overflow-x: auto;
scrollbar-width: none;
box-shadow: 0 0 3px $shadow;
font-size: 0;
}
&::-webkit-scrollbar {
display: none;
.media.center {
width: 1200px;
max-width: 100%;
display: flex;
margin: 0 auto;
}
.poster-links {
display: flex;
position: absolute;
top: 0;
right: 0;
}
.poster-link {
transition: opacity .1s ease;
padding: .5rem;
.icon {
width: 1.5rem;
height: 1.5rem;
fill: var(--lighten-strong);
filter: drop-shadow(0 0 1px var(--darken-weak));
}
&.playing {
opacity: 0;
}
&:first-child {
padding-left: .75rem;
}
&:last-child {
padding-right: .75rem;
}
&:hover {
cursor: pointer;
opacity: 1;
.icon {
fill: var(--text-light);
}
}
}
.trailer {
display: inline-block;
max-width: 100vw;
}
.item-container,
.trailer-container {
height: 100%;
max-width: 100%;
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
.trailer-video {
max-width: 100%;
object-fit: cover;
.warning {
display: none;
width: 100%;
height: 100%;
flex-direction: column;
align-items: center;
justify-content: center;
position: absolute;
background: var(--darken-weak);
color: var(--text-light);
font-size: 1.2rem;
font-weight: bold;
text-shadow: 0 0 3px var(--darken-strong);
pointer-events: none;
animation: alert .5s ease infinite .1s;
.icon {
display: block;
fill: var(--text-light);
width: 3rem;
height: 3rem;
margin: 0 0 .25rem 0;
filter: drop-shadow(0 0 3px var(--darken));
animation: alert .5s ease infinite .1s;
}
}
&:hover .warning {
display: inline-flex;
}
}
.item {
height: 18rem;
vertical-align: middle;
max-width: 100%;
width: auto;
height: 18rem;
box-shadow: 0 0 3px var(--shadow-weak);
background-size: cover;
}
.item-more {
height: auto;
flex-grow: 1;
align-items: center;
padding: .5rem 2rem;
color: var(--text-light);
text-shadow: 0 0 3px var(--darken);
font-weight: bold;
font-size: 1rem;
.link {
color: inherit;
text-decoration: underline;
}
}
.trailer-container {
width: 32rem;
max-width: 100%;
}
.trailer {
width: 100%;
max-width: 32rem;
max-height: 100%;
&.sfw {
filter: blur(2rem);
}
}
@keyframes alert {
0% {
color: var(--text-light);
fill: var(--text-light);
}
50% {
color: var(--alert);
fill: var(--alert);
}
}
@media(max-width: $breakpoint-micro) {
.media.center.preview {
flex-direction: column;
}
.item-more {
font-size: .9rem;
}
.media:not(.expanded) .item,
.trailer-container {
height: 56vw; /* 16:9 ratio for full-width video */
}
}
</style>

View File

@@ -0,0 +1,235 @@
<template>
<div
v-if="timeline"
class="timeline"
>
<ul class="timeline-items nolist">
<li
v-for="chapter in timeline"
:key="`chapter-${chapter.id}`"
:style="{ left: `${(chapter.time / duration) * 100}%` }"
:title="formatDuration(chapter.time)"
class="timeline-item"
><router-link
:to="`/tag/${chapter.tags[0].slug}`"
class="link"
>{{ chapter.tags[0]?.name || '&nbsp;' }}</router-link></li>
</ul>
</div>
<ul
v-else
class="chapters nolist"
>
<li
v-for="chapter in chapters"
:key="`chapter-${chapter.id}`"
class="chapter"
>
<a
v-if="chapter.poster"
:href="getPath(chapter.poster)"
target="_blank"
rel="noopener noreferrer"
>
<img
:src="getPath(chapter.poster, 'thumbnail')"
class="chapter-poster"
>
</a>
<span class="chapter-details">
<span
v-if="chapter.time"
v-tooltip="'Time in video'"
class="chapter-time"
><Icon icon="film3" /> {{ formatDuration(chapter.time) }}</span>
<span
v-if="chapter.duration"
v-tooltip="'Duration'"
class="chapter-duration"
><Icon icon="stopwatch" />{{ formatDuration(chapter.duration) }}</span>
</span>
<div class="chapter-info">
<h3
v-if="chapter.title"
class="chapter-row chapter-title"
:title="chapter.title"
>{{ chapter.title }}</h3>
<p
v-if="chapter.description"
class="chapter-row chapter-description"
>{{ chapter.description }}</p>
<Tags
:tags="chapter.tags"
class="chapter-row chapter-tags"
/>
</div>
</li>
</ul>
</template>
<script>
import Tags from './tags.vue';
function timeline() {
if (this.chapters.every(chapter => chapter.time)) {
return this.chapters.filter(chapter => chapter.tags?.length > 0);
}
return null;
}
export default {
components: {
Tags,
},
props: {
chapters: {
type: Array,
default: () => [],
},
},
data() {
const lastChapter = this.chapters[this.chapters.length - 1];
return {
duration: lastChapter.time + lastChapter.duration,
};
},
computed: {
timeline,
},
};
</script>
<style lang="scss">
.chapter-tags.tags-container {
margin: 0 0 .5rem 0;
.tags {
padding: 2px 0 0 2px;
}
}
</style>
<style lang="scss" scoped>
@import 'breakpoints';
.chapters {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
grid-gap: 1rem;
}
.chapter {
display: flex;
flex-direction: column;
background: var(--background);
box-shadow: 0 0 3px var(--shadow-weak);
margin: 0 0 .5rem 0;
font-size: 0;
}
.chapter-poster {
width: 100%;
height: 10rem;
object-fit: cover;
object-position: center;
}
.chapter-details {
height: 1.75rem;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 .5rem;
margin: 0 0 .75rem 0;
color: var(--text-light);
background: var(--profile);
font-size: .9rem;
font-weight: bold;
.icon {
fill: var(--text-light);
margin: -.1rem .5rem 0 0;
}
}
.chapter-duration,
.chapter-time {
display: flex;
align-items: center;
}
.chapter-duration .icon {
/* narrower icon */
margin: -.1rem .3rem 0 0;
}
.chapter-info {
padding: 0 .5rem;
font-size: 1rem;
}
.chapter-row {
margin: 0 0 .5rem 0;
}
.chapter-title {
padding: 0;
font-size: 1rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.chapter-description {
line-height: 1.5;
}
.timeline-items {
position: relative;
height: 5rem;
border-bottom: solid 1px var(--shadow-weak);
}
.timeline-item {
position: absolute;
bottom: -.25rem;
padding: .1rem .5rem;
border-radius: .6rem;
color: var(--primary);
background: var(--background);
transform: rotate(-60deg);
transform-origin: 0 50%;
box-shadow: 0 0 3px var(--shadow-weak);
font-size: .8rem;
font-weight: bold;
.link {
color: inherit;
}
&:before {
content: '';
display: inline-block;
width: 1rem;
height: 2px;
position: absolute;
left: calc(-1rem + 1px);
margin: .3rem .5rem 0 0;
background: var(--primary);
}
}
@media(max-width: $breakpoint-micro) {
.chapters {
grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
}
}
</style>

View File

@@ -0,0 +1,227 @@
<template>
<div class="details">
<div class="column">
<div class="tidbits">
<a
:title="release.url && `View scene on ${release.entity.name}`"
:href="release.url"
:class="{ link: release.url }"
target="_blank"
rel="noopener noreferrer"
class="tidbit date nolink"
>
<span class="date-compact">{{ release.date ? formatDate(release.date, 'MMM D, YYYY', release.datePrecision) : 'Date N/A' }}</span>
<span class="date-full">{{ release.date ? formatDate(release.date, 'MMMM D, YYYY', release.datePrecision) : 'Date unknown' }}</span>
<Icon
v-if="release.url"
icon="share2"
/>
</a>
</div>
<div class="site">
<template v-if="release.entity.type === 'channel' && release.entity.parent && !release.entity.independent">
<a
v-if="release.entity.parent.hasLogo"
:href="`/network/${release.entity.parent.slug}`"
class="logo-link"
>
<img
:src="`/img/logos/${release.entity.parent.slug}/thumbs/network.png`"
:title="release.entity.parent.name"
:alt="release.entity.parent.name"
class="logo logo-parent"
>
</a>
<a
v-else
:href="`/network/${release.entity.parent.slug}`"
class="logo-link logo-name"
>{{ release.entity.parent.name }}</a>
<span class="chain">presents</span>
<a
v-if="release.entity.hasLogo"
:href="`/${release.entity.type}/${release.entity.slug}`"
class="logo-link"
>
<img
v-if="release.entity.type === 'network'"
:src="`/img/logos/${release.entity.slug}/thumbs/network.png`"
:title="release.entity.name"
class="logo logo-site"
>
<img
v-else
:src="`/img/logos/${release.entity.parent.slug}/thumbs/${release.entity.slug}.png`"
:title="release.entity.name"
class="logo logo-site"
>
</a>
<a
v-else
:href="`/${release.entity.type}/${release.entity.slug}`"
class="logo-link logo-name"
>{{ release.entity.name }}</a>
</template>
<a
v-else
:href="`/${release.entity.type}/${release.entity.slug}`"
>
<img
:src="`/img/logos/${release.entity.slug}/thumbs/network.png`"
:title="release.entity.name"
class="logo logo-site"
>
</a>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
release: {
type: Object,
default: null,
},
},
};
</script>
<style lang="scss" scoped>
@import 'breakpoints';
.details {
background: var(--profile);
color: var(--text-light);
box-shadow: 0 0 3px var(--shadow-weak);
cursor: default;
.column {
height: 100%;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 1rem;
}
.link {
color: var(--text-light);
.icon {
fill: var(--lighten);
}
&:hover {
color: var(--text-light);
.icon {
fill: var(--text-light);
}
}
}
}
.tidbits {
flex-shrink: 0;
height: 100%;
}
.tidbit {
display: inline-flex;
align-items: center;
height: 100%;
&.date {
flex-shrink: 0;
font-weight: bold;
.icon {
fill: var(--lighten);
margin: -.2rem 0 0 .75rem;
}
}
}
.site {
display: inline-flex;
align-items: center;
max-width: 50%;
padding: .25rem 0;
font-size: 0;
}
.logo {
display: inline-block;
}
.logo-link {
text-decoration: none;
}
.logo-site {
height: 2.25rem;
max-width: 15rem;
margin: .25rem 0;
object-fit: contain;
object-position: 100% 50%;
}
.logo-parent {
height: 1.5rem;
max-width: 10rem;
object-fit: contain;
object-position: 100% 50%;
}
.logo-name {
padding: .5rem 0;
color: var(--text-light);
font-size: 1.25rem;
font-weight: bold;
}
.chain {
color: var(--lighten);
padding: 0 .5rem;
font-weight: bold;
font-size: .8rem;
}
.date-compact {
display: none;
}
@media(max-width: $breakpoint-mega) {
.logo-parent,
.chain {
display: none;
}
.logo-site {
height: 1.5rem;
}
.logo-site {
width: 100%;
}
}
@media(max-width: $breakpoint) {
.date-full {
display: none;
}
.date-compact {
display: inline-block;
}
}
</style>

View File

@@ -0,0 +1,303 @@
<template>
<div class="tile">
<Details :release="movie" />
<div class="movie">
<RouterLink
:to="{ name: 'movie', params: { releaseId: movie.id, releaseSlug: movie.slug } }"
class="cover"
>
<img
v-if="movie.covers[0]"
:src="getPath(movie.covers[0], 'thumbnail')"
:style="{ 'background-image': getBgPath(movie.covers[0], 'lazy') }"
loading="lazy"
>
<div
v-else
:title="movie.title"
class="unavailable"
><Icon icon="blocked" /></div>
<Icon
v-show="(!stash || stash.primary) && favorited"
icon="heart7"
class="stash stashed"
@click.prevent.native="unstashMovie"
/>
<Icon
v-show="(!stash || stash.primary) && favorited === false"
icon="heart8"
class="stash unstashed"
@click.prevent.native="stashMovie"
/>
<Icon
v-show="stash && !stash.primary"
icon="cross2"
class="stash unstash"
@click.prevent.native="unstashMovie"
/>
</RouterLink>
<div class="info">
<RouterLink
:to="{ name: 'movie', params: { releaseId: movie.id, releaseSlug: movie.slug } }"
class="title-link"
>
<h3 class="title">{{ movie.title }}</h3>
</RouterLink>
<ul
class="actors nolist"
:title="movie.actors.map(actor => actor.name).join(', ')"
>
<li
v-for="actor in movie.actors"
:key="`tag-${movie.id}-${actor.id}`"
class="actor"
><RouterLink
:to="`/actor/${actor.id}/${actor.slug}`"
class="actor-link"
>{{ actor.name }}</RouterLink></li>
</ul>
<ul
class="tags nolist"
:title="movie.tags.map(tag => tag.name).join(', ')"
>
<li
v-for="tag in movie.tags"
:key="`tag-${movie.id}-${tag.id}`"
class="tag"
><RouterLink
:to="`/tag/${tag.slug}`"
class="tag-link"
>{{ tag.name }}</RouterLink></li>
</ul>
</div>
</div>
</div>
</template>
<script>
import Details from './tile-details.vue';
async function stashMovie() {
this.favorited = true;
try {
await this.$store.dispatch('stashMovie', {
movieId: this.movie.id,
stashId: this.$store.getters.favorites.id,
});
this.$emit('stash', true);
} catch (error) {
this.favorited = false;
}
}
async function unstashMovie() {
if (!this.stash || this.stash.primary) {
this.favorited = false;
}
try {
await this.$store.dispatch('unstashMovie', {
movieId: this.movie.id,
stashId: this.stash?.id || this.$store.getters.favorites.id,
});
this.$emit('stash', false);
} catch (error) {
this.favorited = true;
}
}
function sfw() {
return this.$store.state.ui.sfw;
}
export default {
components: {
Details,
},
props: {
movie: {
type: Object,
default: null,
},
stash: {
type: Object,
default: null,
},
},
data() {
return {
favorited: this.movie.isFavorited,
};
},
computed: {
sfw,
},
methods: {
stashMovie,
unstashMovie,
},
};
</script>
<style lang="scss" scoped>
@import 'breakpoints';
.tile {
display: flex;
flex-direction: column;
background: var(--background);
box-shadow: 0 0 3px var(--darken-weak);
font-size: 0;
&:hover .unstashed,
&:hover .unstash {
fill: var(--lighten);
}
}
.movie {
display: flex;
}
.title-link {
color: var(--text);
text-decoration: none;
}
.cover {
height: 16rem;
width: 11.25rem;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
position: relative;
box-shadow: 0 0 3px var(--darken-weak);
background-color: var(--shadow-hint);
img {
height: 100%;
width: 100%;
background-position: center;
background-size: cover;
object-fit: cover;
object-position: center;
}
.unavailable .icon {
width: 2rem;
height: 2rem;
fill: var(--shadow-hint);
}
}
.info {
display: flex;
flex-direction: column;
flex-grow: 1;
overflow: hidden;
}
.title {
box-sizing: border-box;
padding: 1rem;
margin: 0;
font-size: 1rem;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.actors {
height: 0;
flex-grow: 1;
padding: 0 1rem;
line-height: 1.5;
overflow: hidden;
}
.actor:not(:last-child)::after {
content: ',';
margin: 0 .25rem 0 0;
font-size: 1rem;
}
.actor-link {
font-size: 1rem;
color: var(--link);
text-decoration: none;
&:hover {
color: var(--primary);
}
}
.tags {
padding: .25rem 1rem;
height: 1.75rem;
line-height: 2;
overflow: hidden;
}
.tag {
margin: 0 0 .5rem 0;
}
.tag-link {
background: var(--background);
font-size: .75rem;
padding: .25rem .5rem;
color: var(--shadow);
font-weight: bold;
text-decoration: none;
box-shadow: 0 0 3px var(--shadow-weak);
&:hover {
color: var(--primary);
}
}
.stash {
width: 1.5rem;
height: 1.5rem;
padding: .25rem .5rem .5rem .5rem;
position: absolute;
top: 0;
left: 0;
fill: var(--lighten-weak);
filter: drop-shadow(0 0 2px var(--darken));
&:hover.unstashed,
&.stashed {
fill: var(--primary);
}
&:hover.unstash {
fill: var(--text-light);
filter: drop-shadow(0 0 2px var(--darken-weak));
}
}
@media(max-width: $breakpoint-kilo) {
.cover {
height: 12rem;
width: 8.25rem;
}
/* ensure no half actor names show */
.actors {
margin: 0 0 1rem 0;
}
}
</style>

View File

@@ -0,0 +1,124 @@
<template>
<div class="movies">
<div class="content-inner">
<SearchBar :placeholder="`Search ${totalCount} movies`" />
<div
ref="tiles"
class="tiles"
>
<MovieTile
v-for="movie in movies"
:key="`movie-${movie.id}`"
:movie="movie"
/>
</div>
<Pagination
v-if="totalCount > 0"
:items-total="totalCount"
:items-per-page="limit"
class="pagination-bottom"
/>
</div>
<Footer />
</div>
</template>
<script>
import MovieTile from './movie-tile.vue';
import SearchBar from '../search/bar.vue';
import Pagination from '../pagination/pagination.vue';
async function fetchMovies() {
if (this.$route.query.query) {
await this.searchMovies();
return;
}
const { movies, totalCount } = await this.$store.dispatch('fetchMovies', {
limit: this.limit,
range: this.$route.params.range,
pageNumber: this.$route.params.pageNumber,
});
this.movies = movies;
this.totalCount = totalCount;
this.$refs.tiles.scrollIntoView();
}
async function searchMovies() {
const { movies, totalCount } = await this.$store.dispatch('searchMovies', {
query: this.$route.query.query,
limit: this.limit,
pageNumber: this.$route.params.pageNumber,
});
this.movies = movies;
this.totalCount = totalCount;
this.$refs.tiles.scrollIntoView();
}
async function mounted() {
this.pageTitle = 'Movies';
await this.fetchMovies();
}
export default {
components: {
MovieTile,
SearchBar,
Pagination,
},
data() {
return {
movies: [],
totalCount: 0,
limit: 20,
};
},
watch: {
$route: fetchMovies,
},
mounted,
methods: {
fetchMovies,
searchMovies,
},
};
</script>
<style lang="scss" scoped>
@import 'breakpoints';
.movies {
display: flex;
flex-direction: column;
flex-grow: 1;
}
.content-inner {
padding: 0 1rem;
}
.search {
margin: 1rem 0 0 0;
}
.tiles {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(25rem, 1fr));
grid-gap: 1rem;
padding: 1rem 0;
}
@media(max-width: $breakpoint-kilo) {
.tiles {
grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
}
}
</style>

View File

@@ -1,416 +1,509 @@
<template>
<div
v-if="release"
class="content"
>
<Banner :release="release" />
<div
v-if="release"
ref="content"
class="content"
@scroll="events.emit('scroll', $event)"
>
<Scroll
v-slot="slotProps"
class="scroll-light banner"
:style="{ 'background-image': bannerBackground }"
:expandable="false"
>
<Banner
:release="release"
class="media"
@load="slotProps.loaded"
/>
</Scroll>
<div class="details">
<div class="column">
<a
v-if="release.date"
v-tooltip.bottom="release.url && `View scene on ${release.site.name}`"
:title="release.url && `View scene on ${release.site.name}`"
:href="release.url"
:class="{ link: release.url }"
target="_blank"
rel="noopener noreferrer"
class="tidbit date"
>
<Icon
v-if="isAfter(new Date(), release.date)"
icon="calendar2"
/>
<Details :release="release" />
<Icon
v-else
v-tooltip.bottom="'To be released'"
icon="sun3"
/>
<button
v-if="release.photos?.length > 0 || release.scenesPhotos?.length > 0"
class="album-toggle"
@click="$router.push({ hash: '#album' })"
><Icon icon="grid3" />View album</button>
<span class="showable">{{ formatDate(release.date, 'MMM D, YYYY') }}</span>
<span class="hideable">{{ formatDate(release.date, 'MMMM D, YYYY') }}</span>
</a>
<Album
v-if="showAlbum"
:items="[release.poster, ...(release.photos || []), ...(release.scenesPhotos || [])]"
:title="release.title"
:path="config.media.mediaPath"
@close="$router.replace({ hash: undefined })"
/>
<span
v-if="release.shootId"
v-tooltip.bottom="`Shoot #`"
class="tidbit shoot hideable"
>
<Icon icon="clapboard-play" />
{{ release.shootId }}
</span>
<div class="info column">
<div class="row row-title">
<h2
v-if="release.title"
class="title"
>
{{ release.title }}
<template v-if="release.movies?.[0]?.title && /^scene \d+$/i.test(release.title)"><span class="title-composed">&nbsp;from&nbsp;</span>{{ release.movies[0].title }}</template>
</h2>
<span
v-if="release.duration"
v-tooltip.bottom="`Duration`"
class="tidbit duration hideable"
>
<Icon icon="stopwatch" />
<h2
v-else-if="release.actors.length > 0"
class="title title-composed"
>
{{ release.actors.map(actor => actor.name).join(', ') }} for {{ release.entity.name }}
<Icon
v-tooltip="`This scene has no known official title`"
icon="question2"
/>
</h2>
<span
v-if="release.duration >= 3600"
class="duration-segment"
>{{ Math.floor(release.duration / 3600).toString().padStart(2, '0') }}:</span>
<span class="duration-segment">{{ Math.floor((release.duration % 3600) / 60).toString().padStart(2, '0') }}:</span>
<span class="duration-segment">{{ (release.duration % 60).toString().padStart(2, '0') }}</span>
</span>
<StashButton
:stashed-by="stashedBy"
@stash="(stash) => stashScene(stash)"
@unstash="(stash) => unstashScene(stash)"
/>
</div>
<span class="tidbit site">
<a
v-if="release.site.independent"
:href="`/network/${release.network.slug}`"
>
<img
:src="`/img/logos/${release.network.slug}/network.png`"
:title="release.network.name"
class="logo logo-site"
>
</a>
<div class="row associations">
<ul class="actors nolist">
<li
v-for="actor in release.actors"
:key="actor.id"
>
<Actor :actor="actor" />
</li>
</ul>
</div>
<template v-else>
<a :href="`/network/${release.network.slug}`">
<img
:src="`/img/logos/${release.network.slug}/network.png`"
:title="release.network.name"
:alt="release.network.name"
class="logo logo-network"
>
</a>
<Tags
v-if="release.tags.length > 0"
:tags="release.tags"
/>
<span class="chain">presents</span>
<div
v-if="release.movies?.length > 0 || release.series?.length > 0"
class="row"
>
<span class="row-label">Part of</span>
<a
:href="`/site/${release.site.slug}`"
>
<img
:src="`/img/logos/${release.network.slug}/${release.site.slug}.png`"
:title="release.site.name"
class="logo logo-site"
>
</a>
</template>
<div class="movies">
<router-link
v-for="movie in [...release.movies, ...release.series]"
:key="`movie-${movie.id}`"
:to="{ name: movie.type || 'movie', params: { releaseId: movie.id, releaseSlug: movie.slug } }"
class="movie"
>
<span class="movie-title">{{ movie.title }}</span>
</span>
</div>
</div>
<img
v-if="movie.covers.length > 0 || movie.poster"
:src="getPath(movie.covers[0] || movie.poster, 'thumbnail')"
class="movie-cover"
>
</router-link>
</div>
</div>
<div class="info column">
<h2 class="row title">{{ release.title }}</h2>
<Releases
v-if="release.scenes && release.scenes.length > 0"
:releases="release.scenes"
/>
<div class="row">
<ul class="actors nolist">
<li
v-for="actor in release.actors"
:key="actor.id"
>
<Actor :actor="actor" />
</li>
</ul>
</div>
<div
v-if="release.directors && release.directors.length > 0"
class="row"
>
<span class="row-label">Director</span>
<div v-if="release.scenes && release.scenes.length > 0">
<h3>Scenes</h3>
<router-link
v-for="director in release.directors"
:key="`director-${director.id}`"
class="link director"
:to="`/director/${director.id}/${director.slug}`"
>{{ director.name }}</router-link>
</div>
<Releases
v-if="release.scenes && release.scenes.length > 0"
:releases="release.scenes"
class="row"
/>
</div>
<div
v-if="release.description"
class="row"
>
<span class="row-label">Description</span>
<p class="description">{{ release.description }}</p>
</div>
<div v-if="release.movie">
<h3>Movie</h3>
<div
v-if="release.chapters?.length > 0"
class="row nolist"
>
<span class="row-label">Chapters</span>
<Release :release="release.movie" />
</div>
<Chapters :chapters="release.chapters" />
</div>
<div
v-if="release.tags.length > 0"
class="row"
>
<Icon icon="price-tags3" />
<div class="row row-tidbits">
<div
v-if="release.duration"
class="row-tidbit"
>
<span class="row-label">Duration</span>
<ul class="tags nolist">
<li
v-for="tag in release.tags"
:key="`tag-${tag.slug}`"
class="tag"
>
<a
:href="`/tag/${tag.slug}`"
class="link"
>{{ tag.name }}</a>
</li>
</ul>
</div>
<div class="duration">{{ formatDuration(release.duration) }}</div>
</div>
<div
v-if="release.duration"
class="row duration showable"
>
<Icon icon="stopwatch" />
<div
v-if="release.shootId"
class="row-tidbit"
>
<span class="row-label">Shoot #</span>
{{ release.shootId }}
</div>
<span
v-if="release.duration >= 3600"
class="duration-segment"
>{{ Math.floor(release.duration / 3600).toString().padStart(2, '0') }}:</span>
<span class="duration-segment">{{ Math.floor((release.duration % 3600) / 60).toString().padStart(2, '0') }}:</span>
<span class="duration-segment">{{ (release.duration % 60).toString().padStart(2, '0') }}</span>
</div>
<div
v-if="release.studio"
class="row-tidbit"
>
<span class="row-label">Studio</span>
<p
v-if="release.description"
class="row description"
>
<Icon icon="info2" />
{{ release.description }}
</p>
<router-link
:to="`/studio/${release.studio.slug}`"
class="link studio"
>{{ release.studio.name }}</router-link>
</div>
<div
v-if="release.studio"
class="row"
>
<Icon icon="video-camera2" />
<div
v-if="release.productionDate"
class="row-tidbit"
>
<span class="row-label">Shoot date</span>
{{ formatDate(release.productionDate, 'MMMM D, YYYY') }}
</div>
<a
v-if="release.studio"
:href="release.studio.url"
target="_blank"
rel="noopener noreferrer"
class="link"
>{{ release.studio.name }}</a>
</div>
<div
v-if="release.productionLocation"
class="row-tidbit"
>
<span class="row-label">Location</span>
<span class="location">
<span
v-if="release.productionLocation.city"
class="location-segment"
>{{ release.productionLocation.city }}, </span>
<span
v-if="release.productionLocation.state"
class="location-segment"
>{{ release.productionLocation.state }}, </span>
<span
v-if="release.productionLocation.country"
class="location-segment"
>{{ release.productionLocation.country.alias || release.productionLocation.country.name }}
<img
class="flag"
:src="`/img/flags/${release.productionLocation.country.alpha2.toLowerCase()}.svg`"
>
</span>
</span>
</div>
</div>
<div
v-if="release.shootId"
class="row showable"
>
<Icon icon="clapboard-play" />
<div
v-if="release.comment"
class="row"
>
<span class="row-label">Comment</span>
<span>{{ release.comment }}</span>
</div>
<a
:href="release.url"
:title="`release.shootId`"
target="_blank"
rel="noopener noreferrer"
class="link shoot"
>{{ release.shootId }}</a>
</div>
<div class="row">
<span class="row-label">Added</span>
<span class="row">
<Icon icon="drawer-in" />
<a
:href="`/added/${formatDate(release.dateAdded, 'YYYY-MM-DD')}`"
:title="`Added on ${formatDate(release.dateAdded, 'MMMM D, YYYY')}`"
target="_blank"
rel="noopener noreferrer"
class="link added"
>{{ formatDate(release.dateAdded, 'MMMM D, YYYY') }}</a>
</span>
</div>
</div>
<router-link
:to="`/added/${formatDate(release.createdAt, 'YYYY/MM/DD')}`"
:title="`Added on ${formatDate(release.createdAt, 'MMMM D, YYYY HH:mm')}`"
class="link added"
>{{ release.createdBatchId }}: {{ formatDate(release.createdAt, 'MMMM D, YYYY HH:mm') }}</router-link>
</div>
</div>
</div>
</template>
<script>
import Details from './details.vue';
import Banner from './banner.vue';
import Actor from '../tile/actor.vue';
import Release from '../tile/release.vue';
import StashButton from '../stashes/button.vue';
import Album from '../album/album.vue';
import Tags from './tags.vue';
import Chapters from './chapters.vue';
import Actor from '../actors/tile.vue';
import Releases from './releases.vue';
import Scroll from '../scroll/scroll.vue';
function pageTitle() {
return this.release && this.release.title;
async function fetchRelease(scroll = true) {
if (this.$route.name === 'scene') {
this.release = await this.$store.dispatch('fetchReleaseById', this.$route.params.releaseId);
}
if (this.$route.name === 'movie') {
this.release = await this.$store.dispatch('fetchMovieById', this.$route.params.releaseId);
}
if (this.$route.name === 'serie') {
this.release = await this.$store.dispatch('fetchSerieById', this.$route.params.releaseId);
}
if (scroll && this.$refs.content) {
this.$refs.content.scrollTop = 0;
}
this.stashedBy = this.release.stashes;
}
async function mounted() {
this.release = await this.$store.dispatch('fetchReleaseById', this.$route.params.releaseId);
async function stashScene(stashId) {
this.stashedBy = await this.$store.dispatch(this.$route.name === 'movie' ? 'stashMovie' : 'stashScene', {
sceneId: this.release.id,
movieId: this.release.id,
stashId,
});
}
async function unstashScene(stashId) {
this.stashedBy = await this.$store.dispatch(this.$route.name === 'movie' ? 'unstashMovie' : 'unstashScene', {
sceneId: this.release.id,
movieId: this.release.id,
stashId,
});
}
function me() {
return this.$store.state.auth.user;
}
function bannerBackground() {
return (this.release.poster && this.getBgPath(this.release.poster, 'thumbnail'))
|| (this.release.covers.length > 0 && this.getBgPath(this.release.covers[0], 'thumbnail'));
}
function pageTitle() {
return this.release
&& (this.release.title
|| (this.release.actors.length > 0 ? `${this.release.actors.map((actor) => actor.name).join(', ')} for ${this.release.entity.name}` : null));
}
function showAlbum() {
return (this.release.photos?.length > 0 || this.release.scenesPhotos?.length > 0) && this.$route.hash === '#album';
}
export default {
components: {
Actor,
Banner,
Releases,
Release,
},
data() {
return {
release: null,
};
},
computed: {
pageTitle,
},
mounted,
components: {
Actor,
Album,
Banner,
Chapters,
Details,
Releases,
Scroll,
StashButton,
Tags,
},
data() {
return {
release: null,
stashedBy: [],
};
},
computed: {
pageTitle,
bannerBackground,
me,
showAlbum,
},
watch: {
$route: fetchRelease,
},
mounted: fetchRelease,
methods: {
fetchRelease,
stashScene,
unstashScene,
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
.column {
width: 1200px;
max-width: 100%;
padding: 0 1rem;
margin: 0 auto;
box-sizing: border-box;
@import 'breakpoints';
.expand-bottom {
border-bottom: solid 1px var(--shadow-hint);
}
.banner {
background-position: center;
background-size: cover;
:deep(.scrollable) {
backdrop-filter: blur(1rem);
}
}
.info {
padding: 1rem;
border-left: solid 1px $shadow-hint;
border-right: solid 1px $shadow-hint;
padding: 1rem 0;
border-left: solid 1px var(--shadow-hint);
border-right: solid 1px var(--shadow-hint);
flex-grow: 1;
}
.row {
display: flex;
align-items: center;
padding: 0 1rem;
margin: 0 0 1rem 0;
.icon {
display: inline-block;
width: 1rem;
fill: $shadow-strong;
margin: 0 1rem 0 0;
&.associations {
align-items: start;
}
}
.details {
background: $profile;
color: $text-contrast;
box-shadow: 0 0 3px $shadow-weak;
cursor: default;
.row-label {
display: block;
margin: 0 0 .5rem 0;
color: var(--shadow);
font-weight: bold;
.column {
display: flex;
align-items: center;
padding: 0 1rem;
}
.link {
color: $text-contrast;
}
.icon {
margin: 0 .5rem 0 0;
fill: var(--shadow);
}
}
.tidbit {
display: inline-block;
height: 100%;
&:not(:last-child) {
border-right: solid 1px $highlight-hint;
}
.icon {
fill: $highlight-weak;
margin: 0 .25rem 0 0;
}
&.date,
&.duration,
&.shoot {
flex-shrink: 0;
padding: 1.25rem 1rem 1.25rem 0;
margin: 0 1rem 0 0;
}
.row-tidbit {
display: inline-block;
margin: 0 2rem 0 0;
}
.site {
display: inline-flex;
flex-grow: 1;
align-items: center;
justify-content: flex-end;
padding: .25rem 0;
font-size: 0;
}
.logo {
display: inline-block;
filter: $logo-highlight;
}
.logo-site {
height: 3rem;
max-width: 15rem;
object-fit: contain;
object-position: 100% 50%;
}
.logo-network {
height: 1.5rem;
max-width: 10rem;
object-fit: contain;
object-position: 100% 50%;
}
.chain {
color: $highlight;
padding: 0 .5rem;
font-weight: bold;
font-size: .8rem;
.row-title {
display: flex;
justify-content: space-between;
}
.title {
margin: 0 0 1.5rem 0;
display: inline-flex;
margin: 0;
font-size: 1.5rem;
line-height: 1.25;
.icon {
fill: var(--shadow);
padding: .25rem;
&:hover {
fill: var(--primary);
cursor: pointer;
}
}
}
.title-composed {
color: var(--shadow);
}
.album-toggle {
height: fit-content;
display: inline-flex;
align-items: center;
justify-content: center;
padding: .5rem 1rem;
border: none;
border-bottom: solid 1px var(--shadow-hint);
color: var(--shadow);
background: none;
font-size: 1rem;
font-weight: bold;
.icon {
fill: var(--shadow);
margin: -.1rem .5rem 0 0;
}
&:hover {
background: var(--shadow-hint);
cursor: pointer;
}
}
.description {
line-height: 1.25;
}
.duration {
font-size: 0;
}
.duration-segment {
font-size: 1rem;
line-height: 1.5;
margin: -.25rem 0 0 0;
}
.actors {
display: flex;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
grid-gap: .5rem;
flex-grow: 1;
flex-wrap: wrap;
}
.actor {
width: 10rem;
margin: 0 1rem .5rem 0;
.movies {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
grid-gap: .5rem;
flex-grow: 1;
flex-wrap: wrap;
}
.movie {
display: flex;
flex-direction: column;
background: var(--background);
box-shadow: 0 0 3px var(--shadow-weak);
color: var(--text);
text-decoration: none;
&:hover .movie-title {
color: var(--primary);
}
}
.movie-cover {
width: 100%;
}
.movie-title {
padding: .5rem;
font-weight: bold;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.releases {
margin: 0 0 .5rem 0;
}
.flag {
height: 1rem;
margin: 0 0 -.15rem .1rem;
}
.link {
display: inline-block;
color: $link;
display: inline-flex;
color: var(--link);
text-decoration: none;
&.director:not(:last-child)::after {
content: ', ';
}
&:hover {
color: $primary;
color: var(--primary);
.icon {
fill: $primary;
fill: var(--primary);
}
}
}
.tag .link {
background: $background;
display: inline-block;
padding: .5rem;
margin: 0 .25rem .25rem 0;
box-shadow: 0 0 2px $shadow-weak;
text-decoration: none;
text-transform: capitalize;
&:hover {
color: $primary;
}
}
.showable {
display: none;
}
@media(max-width: $breakpoint3) {
.logo-network,
.chain {
display: none;
@media(max-width: $breakpoint-kilo) {
.releases {
padding: .5rem;
}
}
@@ -419,17 +512,20 @@ export default {
display: none;
}
.row .showable {
display: block;
.row .showable {
display: block;
}
.tidbit .showable {
display: inline-block;
}
.logo-site {
width: 15rem;
max-width: 100%;
.title {
font-size: 1.25rem;
}
.actors {
grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr));
}
}
</style>

View File

@@ -1,60 +1,92 @@
<template>
<div class="releases">
<h3
v-if="context"
class="heading"
><span class="range">{{ range }}</span> releases for '{{ context }}'</h3>
<div class="releases">
<h3
v-if="context"
class="heading"
><span class="range">{{ range }}</span> releases for '{{ context }}'</h3>
<ul class="nolist tiles">
<li
v-for="release in releases"
:key="`release-${release.id}`"
>
<ReleaseTile :release="release" />
</li>
</ul>
<Ellipsis v-if="!done" />
<span
v-if="releases.length === 0 && range !== 'all'"
class="empty"
>No {{ range }} releases</span>
<ul
v-else-if="releases.length > 0"
:key="sfw"
class="nolist tiles"
>
<li
v-for="(release, index) in releases"
:key="`release-${release.id}`"
>
<SceneTile
:release="release"
:referer="referer"
:index="index"
:stash="stash"
@stash="isStashed => $emit('stash', isStashed)"
/>
</li>
</ul>
<span
v-else-if="releases.length === 0"
class="empty"
>No recent or upcoming releases</span>
</div>
<span
v-else-if="releases.length === 0 && range !== 'all'"
class="empty"
>No {{ range }} releases</span>
<span
v-else-if="releases.length === 0"
class="empty"
>No recent or upcoming releases</span>
</div>
</template>
<script>
import ReleaseTile from '../tile/release.vue';
import Ellipsis from '../loading/ellipsis.vue';
import SceneTile from './scene-tile.vue';
function range() {
return this.$store.state.ui.range;
return this.$route.params.range;
}
function sfw() {
return this.$store.state.ui.sfw;
}
export default {
components: {
ReleaseTile,
},
props: {
releases: {
type: Array,
default: () => [],
},
context: {
type: String,
default: null,
},
},
computed: {
range,
},
components: {
Ellipsis,
SceneTile,
},
props: {
releases: {
type: Array,
default: () => [],
},
context: {
type: String,
default: null,
},
done: {
type: Boolean,
default: true,
},
referer: {
type: String,
default: null,
},
stash: {
type: Object,
default: null,
},
},
emits: ['stash'],
computed: {
range,
sfw,
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
@import 'breakpoints';
.heading {
padding: 0;
@@ -65,27 +97,58 @@ export default {
}
}
.releases {
flex-grow: 1;
border-top: solid 1px var(--crease);
&.embedded {
border: none;
.tiles {
padding: 0;
}
}
}
.tiles {
width: 100%;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(20rem, .33fr));
grid-gap: 1rem;
grid-template-columns: repeat(auto-fill, minmax(22rem, 1fr));
grid-gap: .5rem;
box-sizing: border-box;
padding: 1rem;
}
.empty {
color: $shadow-strong;
display: inline-block;
padding: 1rem;
color: var(--shadow-strong);
font-weight: bold;
}
@media(max-width: $breakpoint4) {
@media(max-width: $breakpoint-mega) {
.tiles {
grid-template-columns: repeat(auto-fit, minmax(20rem, .5fr));
grid-template-columns: repeat(auto-fill, minmax(19rem, 1fr));
}
}
@media(max-width: $breakpoint-kilo) {
.tiles {
grid-template-columns: repeat(auto-fill, minmax(18.5rem, 1fr));
grid-gap: .5rem;
padding: .5rem;
}
}
@media(max-width: $breakpoint) {
.tiles {
grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
}
}
@media(max-width: $breakpoint-micro) {
.tiles {
grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
}
}
</style>

View File

@@ -0,0 +1,118 @@
<template>
<a
:href="`/scene/${scene.id}/${scene.slug || ''}`"
target="_blank"
rel="noopener noreferrer"
class="scene nolink"
>
<img
:src="getPath(scene.poster, 'thumbnail')"
class="scene-poster"
>
<div class="scene-header">
<span class="scene-actors nolist">{{ scene.actors.map(actor => actor.name).join(', ') }}</span>
</div>
<div class="scene-footer">
<img
v-if="scene.entity.parent"
:src="`/img/logos/${scene.entity.parent.slug}/favicon_light.png`"
class="scene-favicon"
>
<img
v-else
:src="`/img/logos/${scene.entity.slug}/favicon_light.png`"
class="scene-favicon"
>
<span class="scene-title">{{ scene.title }}</span>
</div>
</a>
</template>
<script>
export default {
props: {
scene: {
type: Object,
default: null,
},
stash: {
type: Object,
default: null,
},
},
};
</script>
<style lang="scss" scoped>
.scene {
width: 14rem;
height: 100%;
position: relative;
font-size: 0;
}
.scene-poster {
width: 100%;
height: 100%;
object-fit: cover;
object-position: 50% 0;
}
.scene-header,
.scene-footer {
width: 100%;
height: 1.25rem;
display: flex;
align-items: center;
position: absolute;
left: 0;
background: var(--darken-weak);
color: var(--text-light);
font-size: .7rem;
font-weight: bold;
overflow: hidden;
text-shadow: 0 0 2px var(--text-dark);
}
.scene-header {
top: 0;
}
.scene-footer {
bottom: 0;
}
.scene-title {
padding: .25rem .5rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.scene-actors {
padding: 0 .5rem;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.scene-unstash {
fill: var(--lighten-strong);
padding: .25rem;
filter: drop-shadow(0 0 1px var(--shadow));
&:hover {
fill: var(--text-light);
}
}
.scene-favicon {
width: 1rem;
height: 1rem;
padding: .1rem 0 0 .25rem;
}
</style>

View File

@@ -0,0 +1,501 @@
<template>
<div
:id="`scene-${release.id}`"
:class="{ new: release.isNew }"
class="tile"
>
<Details
:release="release"
class="details-compact"
/>
<div class="tile-body">
<span class="poster">
<a
:href="`/scene/${release.id}/${release.slug || ''}`"
target="_blank"
rel="noopener noreferrer"
class="link"
>
<img
v-if="release.poster"
:src="getPath(release.poster, 'thumbnail')"
:style="{ 'background-image': getBgPath(release.poster, 'lazy') }"
:alt="release.title"
:width="release.poster.thumbnailWidth"
:height="release.poster.thumbnailHeight"
class="thumbnail"
loading="lazy"
>
<img
v-else-if="release.photos && release.photos.length > 0"
:src="getPath(release.photos[0], 'thumbnail')"
:style="{ 'background-image': getBgPath(release.photos[0], 'lazy') } "
:alt="release.title"
:width="release.photos[0].thumbnailWidth"
:height="release.photos[0].thumbnailHeight"
class="thumbnail"
loading="lazy"
>
<div
v-else
:title="release.title"
class="thumbnail unavailable"
><Icon icon="blocked" />No thumbnail available</div>
<Icon
v-show="(!stash || stash.primary) && favorited"
icon="heart7"
class="stash stashed"
@click.prevent.native="unstashScene"
/>
<Icon
v-show="(!stash || stash.primary) && favorited === false"
icon="heart8"
class="stash unstashed"
@click.prevent.native="stashScene"
/>
<Icon
v-show="stash && !stash.primary"
icon="cross2"
class="stash unstash"
@click.prevent.native="unstashScene"
/>
</a>
</span>
<div class="info">
<Details
:release="release"
class="details-wide"
/>
<a
:href="`/scene/${release.id}/${release.slug || ''}`"
target="_blank"
rel="noopener noreferrer"
class="row link"
>
<h3
v-if="release.title"
v-tooltip.bottom="release.title"
:title="release.title"
class="title"
>
{{ release.title }}<template v-if="release.movies?.[0]?.title && /^scene \d+$/i.test(release.title)"><span class="title-composed">&nbsp;from&nbsp;</span>{{ release.movies[0].title }}</template>
</h3>
<h3
v-else-if="release.actors.length > 0"
class="title title-composed"
>{{ release.actors[0].name }} for {{ release.entity.name }}</h3>
<h3
v-else
class="title title-empty"
>{{ release.entity.name }}</h3>
</a>
<span
v-if="release.actors?.length > 0"
class="row"
>
<ul
class="actors nolist"
:title="release.actors.map(actor => actor.name).join(', ')"
>
<li
v-for="actor in release.actors"
:key="actor.id"
class="actor"
>
<RouterLink
:to="{ name: 'actor', params: { actorId: actor.id, actorSlug: actor.slug } }"
:class="{ [actor.gender]: !!actor.gender }"
class="actor-link"
>{{ actor.name }}</RouterLink>
</li>
</ul>
</span>
<div class="labels">
<RouterLink
v-if="release.shootId && release.studio"
:to="`/studio/${release.studio.slug}`"
:title="release.studio && release.studio.name"
class="shoot nolink"
>{{ release.shootId }}</RouterLink>
<span
v-else-if="release.shootId"
:title="release.studio && release.studio.name"
class="shoot nolink"
>{{ release.shootId }}</span>
<ul
v-if="release.tags?.length > 0"
:title="release.tags.map(tag => tag.name).join(', ')"
class="tags nolist"
>
<li
v-for="tag in release.tags"
:key="`tag-${tag.slug}`"
class="tag"
>
<RouterLink
:to="`/tag/${tag.slug}`"
class="tag-link"
>{{ tag.name }}</RouterLink>
</li>
</ul>
</div>
</div>
</div>
</div>
</template>
<script>
import Details from './tile-details.vue';
async function stashScene() {
this.favorited = true;
try {
await this.$store.dispatch('stashScene', {
sceneId: this.release.id,
stashId: this.$store.getters.favorites.id,
});
this.$emit('stash', true);
} catch (error) {
this.favorited = false;
}
}
async function unstashScene() {
if (!this.stash || this.stash.primary) {
this.favorited = false;
}
try {
await this.$store.dispatch('unstashScene', {
sceneId: this.release.id,
stashId: this.stash?.id || this.$store.getters.favorites.id,
});
this.$emit('stash', false);
} catch (error) {
this.favorited = true;
}
}
export default {
components: {
Details,
},
props: {
release: {
type: Object,
default: null,
},
stash: {
type: Object,
default: null,
},
},
emits: ['stash'],
data() {
return {
favorited: this.release.isFavorited,
};
},
methods: {
stashScene,
unstashScene,
},
};
</script>
<style lang="scss" scoped>
@import 'breakpoints';
.tile {
background: var(--background);
position: relative;
display: flex;
flex-direction: column;
box-sizing: border-box;
overflow: hidden;
height: 100%;
box-shadow: 0 0 3px var(--darken-weak);
&.new .poster::after {
content: 'new';
position: absolute;
display: flex;
align-items: center;
justify-content: center;
bottom: 0;
right: .5rem;
width: 2rem;
box-sizing: border-box;
padding: .1rem 0 .05rem 0;
border-radius: .25rem .25rem 0 0;
background: var(--info);
color: var(--text-light);
font-size: .7rem;
font-weight: bold;
line-height: 1;
box-shadow: 0 0 3px var(--shadow);
}
&:hover .unstashed,
&:hover .unstash {
fill: var(--lighten);
}
}
.tile-body {
display: flex;
flex-direction: column;
}
.poster {
position: relative;
}
.covers {
background: var(--profile);
display: flex;
.cover {
width: 50%;
}
}
.thumbnail {
width: 100%;
height: 14rem;
display: flex;
justify-content: center;
align-items: center;
object-fit: cover;
background-position: center;
background-size: cover;
background-color: var(--shadow-hint);
color: var(--shadow);
text-shadow: 1px 1px 0 var(--highlight);
.icon {
display: none;
width: 2rem;
height: 2rem;
fill: var(--shadow-hint);
}
}
.stash {
width: 1.5rem;
height: 1.5rem;
position: absolute;
top: 0;
right: 0;
padding: .25rem .25rem .5rem .5rem;
filter: drop-shadow(0 0 2px var(--darken));
fill: var(--lighten-weak);
&:hover.unstashed,
&.stashed {
fill: var(--primary);
filter: drop-shadow(0 0 2px var(--darken-weak));
}
&:hover.unstash {
fill: var(--text-light);
filter: drop-shadow(0 0 2px var(--darken-weak));
}
}
.row {
max-width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
box-sizing: border-box;
padding: 0 .5rem;
}
.info {
display: flex;
flex-direction: column;
align-items: flex-start;
flex-grow: 1;
overflow: hidden;
}
.link {
text-decoration: none;
}
.title {
margin: 0;
color: var(--text);
font-size: .9rem;
line-height: 1.5;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.title-composed,
.title-empty {
color: var(--shadow);
}
.actors {
word-wrap: break-word;
overflow: hidden;
max-height: 1.5rem;
line-height: 1.5rem;
margin: 0 0 .25rem 0;
}
.actor:not(:last-of-type)::after {
content: ",";
margin: 0 .25rem 0 0;
}
.actor-link {
color: var(--text);
text-decoration: none;
font-size: .9rem;
&:hover {
color: var(--primary);
}
}
.labels {
padding: 0 .5rem 1.25rem .5rem;
max-height: .5rem;
overflow-y: hidden;
font-size: 0;
line-height: 2;
}
.shoot {
display: inline;
padding: .25rem .5rem;
background: var(--primary);
color: var(--text-light);
font-size: 0.75rem;
font-weight: bold;
box-shadow: inset 0 0 3px var(--shadow-weak);
}
.tags {
display: inline;
word-wrap: break-word;
}
.tag {
margin: 0 0 1rem 0;
&:not(:first-child) .tag-link {
border-left: none;
}
}
.tag-link {
background: var(--shadow-touch);
color: var(--shadow);
display: inline-block;
padding: .25rem .5rem;
margin: 0 1px 0 0;
font-size: .75rem;
font-weight: bold;
text-decoration: none;
line-height: 1;
&:hover {
color: var(--primary);
}
}
.details-wide {
margin: 0 0 .4rem 0;
}
.details-compact {
display: none;
}
@media(max-width: $breakpoint) {
.thumbnail {
height: 11rem;
}
}
@media(max-width: $breakpoint-micro) {
.tile-body {
flex-direction: row;
}
.poster {
margin: 0;
}
.thumbnail {
width: 9rem;
height: 6rem;
font-size: 0;
box-shadow: 0 0 3px var(--shadow-weak);
.icon {
display: block;
}
}
.info {
padding: .5rem .25rem 0 .25rem;
}
.row {
margin: 0 0 .15rem 0;
}
.title,
.actor-link {
font-size: .9rem;
}
.details-wide {
display: none;
}
.details-compact {
display: flex;
}
.shoot {
display: none;
}
.tile.new .poster::after {
top: 0;
right: .25rem;
bottom: auto;
border-radius: 0 0 .25rem .25rem;
padding: .05rem 0 .1rem 0;
}
.stash {
left: 0;
padding: .25rem .5rem .5rem .25rem;
}
}
</style>

View File

@@ -0,0 +1,136 @@
<template>
<div
class="tags-container"
:class="{ overflowing }"
>
<ul
ref="tags"
class="tags nolist"
:class="{ expanded }"
>
<li
v-for="tag in tags"
:key="`tag-${tag.slug}`"
class="tag"
>
<a
:href="`/tag/${tag.slug}`"
class="link"
>{{ tag.name }}</a>
</li>
</ul>
<button
v-if="overflowing && !expanded"
class="tags-more"
@click="expanded = true"
>more tags</button>
<button
v-if="expanded"
class="tags-more"
@click="expanded = false"
>fewer tags</button>
</div>
</template>
<script>
import { nextTick } from 'vue';
function updateOverflowing() {
nextTick(() => {
const containerBoundaries = this.$refs.tags.getBoundingClientRect();
const containerBottom = containerBoundaries.top + containerBoundaries.height;
this.overflowing = Array.from(this.$refs.tags.querySelectorAll('.tag')).some((tag) => {
const tagBoundaries = tag.getBoundingClientRect();
return tagBoundaries.top > containerBottom;
});
});
}
function mounted() {
window.addEventListener('resize', this.updateOverflowing);
this.updateOverflowing();
}
function beforeUnmount() {
window.removeEventListener('resize', this.updateOverflowing);
}
export default {
props: {
tags: {
type: Array,
default: () => [],
},
},
data() {
return {
overflowing: false,
expanded: false,
};
},
watch: {
expanded: updateOverflowing,
},
mounted,
beforeUnmount,
methods: {
updateOverflowing,
},
};
</script>
<style lang="scss" scoped>
.tags-container {
margin: 0 0 1.5rem 0;
&.overflowing {
margin: 0 0 .5rem 0;
}
}
.tags {
max-height: 4.7rem;
padding: 2px 1rem 0 1rem;
text-align: left;
overflow: hidden;
&.expanded {
max-height: unset;
}
}
.tag .link {
color: var(--link);
background: var(--background);
display: inline-block;
padding: .5rem;
margin: 0 .25rem .25rem 0;
box-shadow: 0 0 2px var(--shadow-weak);
text-decoration: none;
text-transform: capitalize;
&:hover {
color: var(--primary);
}
}
.tags-more {
background: var(--shadow-touch);
padding: .5rem 1rem;
border: none;
margin: .25rem 0 .5rem 1rem;
color: var(--shadow);
font-size: .8rem;
font-weight: bold;
&:hover {
background: var(--shadow-hint);
color: var(--shadow-strong);
cursor: pointer;
}
}
</style>

View File

@@ -0,0 +1,171 @@
<template>
<span
class="details"
:class="{ new: release.isNew }"
>
<span
v-if="release.entity && release.entity.type !== 'network' && !release.entity.independent && release.entity.parent"
class="site"
>
<router-link
v-tooltip.bottom="`Part of ${release.entity.parent.name}`"
:title="`Part of ${release.entity.parent.name}`"
:to="`/${release.entity.parent.type}/${release.entity.parent.slug}`"
class="site-link"
>
<img
:src="`/img/logos/${release.entity.parent.slug}/favicon_light.png`"
class="favicon favicon-light"
>
<img
:src="`/img/logos/${release.entity.parent.slug}/favicon_dark.png`"
class="favicon favicon-dark"
>
</router-link>
<router-link
v-tooltip.bottom="`More from ${release.entity.name}`"
:title="`More from ${release.entity.name}`"
:to="`/${release.entity.type}/${release.entity.slug}`"
class="site-link"
>{{ release.entity.name }}</router-link>
</span>
<router-link
v-else-if="release.entity"
:to="`/${release.entity.type}/${release.entity.slug}`"
class="site site-link"
>
<img
:src="`/img/logos/${release.entity.slug}/favicon_light.png`"
class="favicon favicon-light"
>
<img
:src="`/img/logos/${release.entity.slug}/favicon_dark.png`"
class="favicon favicon-dark"
>{{ release.entity.name }}
</router-link>
<a
v-if="release.date"
v-tooltip.bottom="release.url && `View release on ${release.entity.name}`"
:title="release.url && `View release on ${release.entity.name}`"
:href="release.url"
:class="{ upcoming: isAfter(release.date, new Date()) }"
target="_blank"
rel="noopener noreferrer"
class="date"
>{{ formatDate(release.date, 'MMMM D, YYYY', release.datePrecision) }}</a>
<a
v-else
:href="release.url"
title="Scene date N/A, showing date added"
target="_blank"
rel="noopener noreferrer"
class="date"
>{{ `(${formatDate(release.createdAt, 'MMMM D, YYYY')})` }}</a>
</span>
</template>
<script>
export default {
props: {
release: {
type: Object,
default: null,
},
},
};
</script>
<style lang="scss" scoped>
@import 'breakpoints';
.details {
width: 100%;
display: flex;
justify-content: space-between;
z-index: 1;
white-space: nowrap;
background: var(--info);
font-size: 0;
font-weight: bold;
.favicon {
width: 2rem;
box-sizing: border-box;
padding: .4rem .5rem;
}
}
.site,
.date {
display: flex;
align-items: center;
position: relative;
font-size: .8rem;
padding: .4rem .5rem;
color: var(--text-light);
text-decoration: none;
.icon {
fill: var(--lighten-weak);
margin: 0 .25rem 0 0;
}
&:hover .icon {
fill: var(--text-light);
}
}
.site {
padding: 0 .5rem 0 0;
}
.site-link {
display: flex;
color: var(--text-light);
text-decoration: none;
}
.favicon-dark {
display: none;
}
@media(max-width: $breakpoint-kilo) {
/* light details bar
.details {
background: var(--background);
box-shadow: none;
.favicon-dark {
display: inline-block;
}
.favicon-light {
display: none;
}
}
.site-link,
.date {
color: var(--text);
}
*/
.details .favicon {
padding: .35rem .5rem .35rem .5rem;
}
.date {
padding: .35rem .5rem;
}
.site {
padding: 0 .5rem 0 0;
}
}
</style>

View File

@@ -0,0 +1,251 @@
<template>
<div class="scroll">
<Expand
v-if="expanded"
:expanded="expanded"
class="expand-light"
@expand="(state) => $emit('expand', state)"
/>
<Expand
v-if="expanded"
:expanded="expanded"
class="expand-dark"
@expand="(state) => $emit('expand', state)"
/>
<div class="scrollable">
<div
v-show="enabled && !expanded"
class="scroll-button scroll-left noselect"
:class="{ 'scroll-start': scrollAtStart }"
@click="scroll('left')"
><Icon icon="arrow-left3" /></div>
<div
ref="content"
class="scroll-content"
>
<slot :loaded="loaded" />
</div>
<div
v-show="enabled && !expanded"
class="scroll-button scroll-right noselect"
:class="{ 'scroll-end': scrollAtEnd }"
@click="scroll('right')"
><Icon icon="arrow-right3" /></div>
</div>
<Expand
v-if="expanded || (expandable && scrollable)"
:expanded="expanded"
class="expand-light"
@expand="(state) => $emit('expand', state)"
/>
<Expand
v-if="expanded || (expandable && scrollable)"
:expanded="expanded"
class="expand-dark"
@expand="(state) => $emit('expand', state)"
/>
<button
v-if="album && items && items.length > 0 && scrollable"
class="album-toggle"
@click="showAlbum = true"
><Icon icon="grid3" />View album</button>
</div>
</template>
<script>
import Expand from '../expand/expand.vue';
function updateScroll() {
this.scrollable = this.$refs.content.scrollWidth > this.$refs.content.clientWidth;
this.scrollAtStart = this.$refs.content.scrollLeft === 0;
this.scrollAtEnd = this.$refs.content.scrollWidth - this.$refs.content.clientWidth === this.$refs.content.scrollLeft;
}
function scroll(direction) {
if (direction === 'right') {
this.$refs.content.scrollLeft = this.$refs.content.scrollLeft + this.$refs.content.clientWidth - 100;
}
if (direction === 'left') {
this.$refs.content.scrollLeft = this.$refs.content.scrollLeft - this.$refs.content.clientWidth + 100;
}
}
function loaded(_event) {
// typically triggered by slotted component when an image loads, affecting scrollWidth
this.updateScroll();
}
function mounted() {
this.$refs.content.addEventListener('scroll', () => this.updateScroll());
window.addEventListener('resize', this.updateScroll);
this.updateScroll();
}
function beforeUnmount() {
this.$refs.content.removeEventListener('scroll', this.updateScroll);
window.removeEventListener('resize', this.updateScroll);
}
function updated() {
this.updateScroll();
}
export default {
components: {
Expand,
},
props: {
enabled: {
type: Boolean,
default: true,
},
expandable: {
type: Boolean,
default: false,
},
expanded: {
type: Boolean,
default: false,
},
album: {
type: Boolean,
default: false,
},
items: {
type: Array,
default: null,
},
},
data() {
return {
scrollable: true,
scrollAtStart: true,
scrollAtEnd: false,
};
},
mounted,
updated,
beforeUnmount,
methods: {
scroll,
loaded,
updateScroll,
},
};
</script>
<style lang="scss" scoped>
@import 'breakpoints';
.scroll.expanded {
padding: 0;
.scroll {
display: none;
}
}
.scrollable {
position: relative;
}
.scroll-content {
overflow-x: scroll;
scroll-behavior: smooth;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
.scroll-button {
height: 100%;
display: flex;
align-items: center;
box-sizing: border-box;
position: absolute;
top: 0;
bottom: 0;
z-index: 10;
&.scroll-start,
&.scroll-end {
/* use over v-show so button stays visible while still hovered */
display: none;
}
&.scroll-start {
left: 0;
}
&.scroll-end {
right: 0;
}
.icon {
width: 1.5rem;
height: 1.5rem;
fill: var(--lighten);
}
&.scroll-start .icon,
&.scroll-end .icon {
fill: var(--lighten-weak);
}
&:hover:not(.scroll-start):not(.scroll-end) .icon {
fill: var(--lighten-strong);
}
&:hover {
display: flex;
cursor: pointer;
}
}
.scroll-left {
left: 0;
padding: 1rem 2rem 1rem .5rem;
}
.scroll-right {
right: 0;
padding: 1rem .5rem 1rem 2rem;
}
.scroll .expand-light {
display: none;
}
.scroll-light {
.expand-light {
display: block;
}
.expand-dark {
display: none;
}
}
.scroll-dark .expand-light {
display: none;
}
@media(max-width: $breakpoint-micro) {
/* buttons block swiping motion */
.scroll-button {
display: none;
}
}
</style>

View File

@@ -0,0 +1,101 @@
<template>
<form
class="search"
@submit.prevent="() => search()"
>
<input
v-model="query"
:placeholder="placeholder || 'Search'"
class="query"
@input="() => search(true)"
>
<button
type="submit"
class="search-button"
><Icon icon="search" /></button>
</form>
</template>
<script>
function search(typing) {
if (!typing || this.eager) {
this.$router.replace({
query: { query: this.query || undefined },
params: { ...this.$route.params, pageNumber: 1 },
});
}
}
function resetQuery() {
this.query = this.$route.query.query || null;
}
export default {
props: {
placeholder: {
type: String,
default: null,
},
eager: {
type: Boolean,
default: false,
},
},
data() {
return {
query: this.$route.query.query || null,
};
},
watch: {
$route: resetQuery,
},
methods: {
search,
},
};
</script>
<style lang="scss" scoped>
.search {
display: flex;
width: 100%;
}
.query {
max-width: 40rem;
min-width: 10rem;
color: var(--text);
background: var(--background);
flex-grow: 1;
box-sizing: border-box;
padding: 1rem;
border: none;
box-sizing: border-box;
box-shadow: 0 0 3px var(--darken-weak);
font-size: 1rem;
outline: none;
&:focus {
box-shadow: 0 0 3px var(--primary);
}
}
.search-button {
padding: 1rem;
background: none;
border: none;
.icon {
fill: var(--shadow);
}
&:hover {
cursor: pointer;
.icon {
fill: var(--primary);
}
}
}
</style>

View File

@@ -0,0 +1,118 @@
<template>
<div class="content-inner">
<span
v-if="loading"
class="summary"
>Searching...</span>
<template v-if="actors.length > 0">
<span class="summary">Found {{ actors.length }} actors for '{{ query }}'</span>
<div class="tiles">
<Actor
v-for="actor in actors"
:key="`actor-${actor.id}`"
:actor="actor.aliasFor || actor"
:alias="actor.aliasFor && actor"
/>
</div>
</template>
<template v-if="releases.length > 0">
<span class="summary">Found {{ releases.length }} releases for '{{ query }}'</span>
<Releases
class="embedded"
:releases="releases"
/>
</template>
<span
v-if="!loading && actors.length === 0 && releases.length === 0"
class="summary"
>No results</span>
</div>
</template>
<script>
import Actor from '../actors/tile.vue';
import Releases from '../releases/releases.vue';
async function search() {
const results = await this.$store.dispatch('search', {
query: this.query,
limit: 10,
});
this.loading = false;
if (results) {
this.actors = results.actors;
this.releases = results.releases;
}
}
function query() {
return this.$route.query.query || this.$route.query.q;
}
async function mounted() {
await this.search();
}
async function watchQuery() {
await this.search();
}
export default {
components: {
Actor,
Releases,
},
data() {
return {
loading: true,
actors: [],
releases: [],
};
},
computed: {
query,
},
watch: {
query: watchQuery,
},
mounted,
methods: {
search,
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
.content-inner {
padding: 1rem;
}
.summary {
display: block;
margin: 0 0 1rem 0;
color: var(--shadow);
font-weight: bold;
}
.tiles {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
grid-gap: 0 .5rem;
margin: 0 0 1rem 0;
}
@media(max-width: $breakpoint0) {
.tiles {
grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
}
}
</style>

View File

@@ -0,0 +1,415 @@
<template>
<div
class="sidebar-container"
@click="$emit('toggleSidebar', false)"
>
<div
class="sidebar"
@click.stop
>
<div class="sidebar-section">
<div class="sidebar-header">
<router-link
to="/updates"
class="logo-link"
@click="$emit('toggleSidebar', false)"
>
<h1 class="sidebar-logo">
<div
class="logo"
v-html="logo"
/>
</h1>
</router-link>
<Icon
icon="cross2"
class="sidebar-close noselect"
@click.native="$emit('toggleSidebar', false)"
/>
</div>
<Search
class="search"
@search="$emit('toggleSidebar', false)"
/>
<nav class="nav">
<ul class="nolist">
<li
class="nav-item"
@click="$emit('toggleSidebar', false)"
>
<router-link
v-slot="{ href, isActive, navigate }"
to="/updates"
custom
>
<a
class="nav-link"
:href="href"
:class="{ active: isActive }"
@click="navigate"
>Home</a>
</router-link>
</li>
<li
class="nav-item"
@click="$emit('toggleSidebar', false)"
>
<router-link
v-slot="{ href, isActive, navigate }"
to="/actors"
custom
>
<a
class="nav-link"
:href="href"
:class="{ active: isActive }"
@click="navigate"
>Actors</a>
</router-link>
</li>
<li
class="nav-item"
@click="$emit('toggleSidebar', false)"
>
<router-link
v-slot="{ href, isActive, navigate }"
to="/channels"
custom
>
<a
class="nav-link"
:href="href"
:class="{ active: isActive }"
@click="navigate"
>Channels</a>
</router-link>
</li>
<li
class="nav-item"
@click="$emit('toggleSidebar', false)"
>
<router-link
v-slot="{ href, isActive, navigate }"
to="/movies"
custom
>
<a
class="nav-link"
:href="href"
:class="{ active: isActive }"
@click="navigate"
>Movies</a>
</router-link>
</li>
<li
class="nav-item"
@click="$emit('toggleSidebar', false)"
>
<router-link
v-slot="{ href, isActive, navigate }"
to="/tags"
custom
>
<a
class="nav-link"
:href="href"
:class="{ active: isActive }"
@click="navigate"
>Tags</a>
</router-link>
</li>
</ul>
</nav>
</div>
<div class="sidebar-section controls noselect">
<label
v-if="login && me"
@click="$emit('toggleSidebar', false)"
>
<router-link
:to="{ name: 'user', params: { username: me.username } }"
class="toggle username nolink"
>{{ me.username }}</router-link>
</label>
<div class="toggles noselect">
<label
v-if="login && !me"
@click="$emit('toggleSidebar', false)"
>
<router-link
:to="{ name: 'login', query: { ref: $route.path } }"
class="toggle nolink"
><Icon icon="enter2" />Log in</router-link>
</label>
<label
v-if="login && me"
class="toggle"
@click.stop="$store.dispatch('logout')"
><Icon icon="exit2" />Log out</label>
<label
v-show="sfw"
class="toggle"
@click="setSfw(false)"
><Icon icon="fire" />Disable safe mode</label>
<label
v-show="!sfw"
class="toggle"
@click="setSfw(true)"
><Icon icon="flower" />Enable safe mode</label>
<label
v-show="theme === 'dark'"
class="toggle"
@click="setTheme('light')"
><Icon icon="sun" />Use light theme</label>
<label
v-show="theme === 'light'"
class="toggle"
@click="setTheme('dark')"
><Icon icon="moon" />Use dark theme</label>
<label
class="toggle"
@click="$emit('showFilters', true)"
><Icon icon="filter" />Filters</label>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex';
import Search from '../header/search.vue';
import logo from '../../img/logo.svg';
function sfw(state) {
return state.ui.sfw;
}
function theme(state) {
return state.ui.theme;
}
function login(state) {
return state.auth.login;
}
function signup(state) {
return state.auth.signup;
}
function me(state) {
return state.auth.user;
}
function setTheme(newTheme) {
this.$store.dispatch('setTheme', newTheme);
}
function setSfw(enabled) {
this.$store.dispatch('setSfw', enabled);
}
export default {
components: {
Search,
},
emits: ['toggleSidebar', 'showFilters'],
data() {
return {
logo,
};
},
computed: {
...mapState({
login,
signup,
me,
sfw,
theme,
}),
},
methods: {
setTheme,
setSfw,
},
};
</script>
<style lang="scss" scoped>
.sidebar-container {
height: 100%;
width: 100%;
position: absolute;
z-index: 10;
background: var(--darken-weak);
}
.sidebar {
display: flex;
flex-direction: column;
justify-content: space-between;
width: 15rem;
height: 100%;
margin: 0 0 0 auto;
color: var(--text);
background: var(--background);
box-shadow: 0 0 3px var(--darken-weak);
}
.sidebar-header {
display: flex;
justify-content: space-between;
height: 3rem;
border-bottom: solid 1px var(--shadow-hint);
}
.sidebar-close {
width: 1.25rem;
height: 100%;
padding: 0 1.125rem;
fill: var(--shadow-modest);
&:hover {
fill: var(--primary);
cursor: pointer;
}
}
.sidebar-logo {
height: 100%;
display: flex;
align-items: center;
margin: 0;
}
.sidebar-section {
display: flex;
flex-direction: column;
overflow: hidden;
}
.logo-link {
display: block;
height: 100%;
padding: 0 1rem;
}
.logo {
width: 6rem;
display: flex;
align-items: center;
margin: 0;
fill: var(--primary);
}
:deep(.search) {
height: 3rem;
border-bottom: solid 1px var(--shadow-hint);
padding: 0;
margin: 0 0 .5rem 0;
.search-input {
padding: .5rem 0 .5rem 1rem;
}
}
.nav {
flex-grow: 1;
overflow-x: auto;
}
.nav-item {
display: block;
}
.nav-link {
color: var(--shadow);
display: block;
padding: 1rem;
text-decoration: none;
font-weight: bold;
&:hover {
color: var(--shadow-strong);
}
&.active {
color: var(--primary);
}
}
.controls {
margin: .5rem 0 0 0;
}
.toggles {
flex-shrink: 0;
border-top: solid 1px var(--shadow-hint);
}
.toggle {
display: flex;
padding: 1rem;
color: var(--shadow);
font-weight: bold;
&.username {
justify-content: center;
}
.icon {
fill: var(--shadow);
margin: 0 1rem 0 0;
}
&.active .icon {
fill: var(--primary);
}
&:hover {
cursor: pointer;
color: var(--shadow-strong);
&:not(.active) .icon {
fill: var(--shadow-strong);
}
}
}
.dark .sidebar {
background: var(--profile);
.nav-link {
color: var(--shadow);
&.active {
color: var(--text-light);
}
}
.sidebar-close {
fill: var(--lighten);
&:hover {
fill: var(--text-light);
}
}
}
</style>

View File

@@ -1,145 +0,0 @@
<template>
<div
v-if="site"
class="content site"
>
<FilterBar :fetch-releases="fetchSite" />
<div class="header">
<a
v-tooltip.bottom="site.url && `Go to ${site.url}`"
:href="site.url"
target="_blank"
rel="noopener noreferrer"
class="link link-site"
>
<img
:src="`/img/logos/${site.network.slug}/${site.slug}.png`"
:title="site.name"
:alt="site.name"
class="logo logo-site"
>
</a>
<ul class="tags nolist">
<li
v-for="tag in site.tags"
:key="`tag-${tag.slug}`"
class="tag"
>{{ tag.name }}</li>
</ul>
<a
v-tooltip.bottom="`Go to ${site.network.name} overview`"
:href="`/network/${site.network.slug}`"
class="link link-network"
>
<img
:src="`/img/logos/${site.network.slug}/network.png`"
:title="site.network.name"
:alt="site.network.name"
class="logo logo-network"
>
</a>
</div>
<div class="content-inner">
<Releases :releases="releases" />
</div>
</div>
</template>
<script>
import FilterBar from '../header/filter-bar.vue';
import Releases from '../releases/releases.vue';
async function fetchSite() {
this.site = await this.$store.dispatch('fetchSites', { siteSlug: this.$route.params.siteSlug });
this.releases = this.site.releases;
}
async function mounted() {
await this.fetchSite();
this.pageTitle = this.site.name;
}
export default {
components: {
FilterBar,
Releases,
},
data() {
return {
site: null,
releases: null,
pageTitle: null,
};
},
mounted,
methods: {
fetchSite,
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
.header {
background: $profile;
color: $text-contrast;
display: flex;
align-items: center;
justify-content: space-between;
}
.link {
padding: 1rem;
}
.link-site {
margin: 0 1rem 0 0;
justify-content: flex-start;
.logo {
object-position: 0 0;
}
}
.link-network {
justify-content: flex-end;
.logo {
object-position: 100% 0;
}
}
.logo {
width: 100%;
max-width: 15rem;
max-height: 5rem;
object-fit: contain;
filter: $logo-highlight;
}
.tag {
background: $shadow;
padding: .5rem;
margin: 0 .5rem .5rem 0;
}
@media(max-width: $breakpoint) {
.link {
padding: .5rem 1rem;
}
.logo {
max-height: 2.5rem;
}
.tags {
display: none;
}
}
</style>

View File

@@ -1,93 +0,0 @@
<template>
<div class="sites">
<ul class="nolist tiles">
<li
v-for="site in sites"
:key="`site-${site.id}`"
class="site"
>
<SiteTile :site="site" />
</li>
</ul>
</div>
</template>
<script>
import SiteTile from '../tile/site.vue';
export default {
components: {
SiteTile,
},
props: {
network: {
type: Object,
default: null,
},
sites: {
type: Array,
default: () => [],
},
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
.sites {
overflow: hidden;
display: flex;
&.compact:not(.expanded) {
flex-direction: row;
.tiles {
display: flex;
overflow-x: auto;
}
.tile {
width: 15rem;
margin: 0 1rem 0 0;
}
}
&.expanded {
.tiles {
grid-template-columns: repeat(auto-fit, minmax(15rem, .5fr));
}
&.compact .tiles {
padding: 0 1rem 1rem 1rem;
}
}
}
.tiles {
display: grid;
grid-gap: 0 1rem;
flex-grow: 1;
padding: 1rem;
grid-template-columns: 1fr;
overflow-y: auto;
scrollbar-color: $highlight-weak $profile;
}
.site {
/* vertical grid-gap not compatible with bottom padding on scrolling containers */
margin: 0 0 1rem 0;
}
@media(max-width: $breakpoint3) {
.sites.expanded .tiles {
grid-template-columns: repeat(auto-fit, minmax(12rem, .5fr));
}
}
@media(max-width: $breakpoint0) {
.sites.expanded .tiles {
grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
}
}
</style>

View File

@@ -0,0 +1,53 @@
<template>
<Dialog
title="Add stash"
@close="$emit('close', false)"
>
<form
class="dialog-body"
@submit.prevent="addStash"
>
<input
ref="name"
v-model="name"
type="input"
placeholder="Name"
class="input"
>
<div class="dialog-actions right">
<button
type="submit"
class="button button-primary"
>Add</button>
</div>
</form>
</Dialog>
</template>
<script>
async function addStash() {
await this.$store.dispatch('createStash', {
name: this.name,
});
this.$emit('close', true);
}
function mounted() {
this.$refs.name.focus();
}
export default {
data() {
return {
name: null,
};
},
emits: ['close'],
mounted,
methods: {
addStash,
},
};
</script>

View File

@@ -0,0 +1,112 @@
<template>
<span class="stash-container">
<Tooltip class="stash-trigger">
<Icon
v-show="me"
icon="menu"
class="stash noselect"
:class="{ stashed }"
/>
<template v-slot:tooltip>
<StashMenu
:stashed-by="stashedBy"
@stash="(stashId) => $emit('stash', stashId)"
@unstash="(stashId) => $emit('unstash', stashId)"
/>
</template>
</Tooltip>
<Icon
v-show="me && favorited"
icon="heart7"
class="stash stashed noselect"
@click.native="() => $emit('unstash', favorites.id)"
/>
<Icon
v-show="me && !favorited"
icon="heart8"
class="stash unstashed noselect"
@click.native="() => $emit('stash', favorites.id)"
/>
</span>
</template>
<script>
import StashMenu from './menu.vue';
function favorited() {
return this.stashedBy.some(stash => stash.primary);
}
function stashed() {
return this.stashedBy.some(stash => !stash.primary);
}
function favorites() {
return this.$store.getters.favorites;
}
function me() {
return this.$store.state.auth.user;
}
export default {
components: {
StashMenu,
},
props: {
stashedBy: {
type: Array,
default: () => [],
},
},
emits: ['stash', 'unstash'],
computed: {
me,
favorites,
favorited,
stashed,
},
};
</script>
<style lang="scss" scoped>
@import 'breakpoints';
.stash-container {
flex-shrink: 0;
&.light .icon {
fill: var(--lighten);
}
}
.stash.icon {
width: 1.5rem;
height: 1.5rem;
padding: 0 .5rem;
fill: var(--shadow);
&.stashed {
fill: var(--primary);
}
&:hover {
fill: var(--primary);
cursor: pointer;
}
}
.stash-trigger {
display: inline-block;
}
@media(max-width: $breakpoint) {
.stash.icon {
width: 1.25rem;
height: 1.25rem;
}
}
</style>

View File

@@ -0,0 +1,71 @@
<template>
<ul class="menu nolist">
<li
v-for="stash in stashes"
:key="`stash-${stash.id}`"
class="menu-item"
>
<label class="menu-stash noselect">
<Checkbox
:checked="stashedByIds.has(stash.id)"
class="menu-check"
@change="(checked) => checked ? $emit('stash', stash.id) : $emit('unstash', stash.id)"
/>{{ stash.name }}
</label>
</li>
</ul>
</template>
<script>
import Checkbox from '../form/checkbox.vue';
function stashes() {
return this.$store.state.auth.user?.stashes || [];
}
export default {
components: {
Checkbox,
},
props: {
stashedBy: {
type: Array,
default: () => [],
},
},
emits: ['stash', 'unstash'],
data() {
const stashedByIds = new Set(this.stashedBy.map(stash => stash.id));
return {
stashedByIds,
};
},
computed: {
stashes,
},
};
</script>
<style lang="scss" scoped>
.menu-item {
color: var(--text);
display: block;
}
.menu-stash {
display: flex;
align-items: center;
padding: .5rem 1rem .5rem .5rem;
&:hover {
color: var(--primary);
cursor: pointer;
}
}
.menu-check {
display: inline-block;
margin: 0 .75rem 0 0;
}
</style>

View File

@@ -0,0 +1,41 @@
<template>
<Dialog
title="Remove stash"
@close="$emit('close', false)"
>
<form
class="dialog-body"
@submit.prevent="removeStash"
>
Are you sure you want to remove stash "{{ stash.name }}"?
<div class="dialog-actions right">
<button
type="submit"
class="button button-primary"
>Remove</button>
</div>
</form>
</Dialog>
</template>
<script>
async function removeStash() {
await this.$store.dispatch('removeStash', this.stash.id);
this.$emit('close', true);
}
export default {
props: {
stash: {
type: Object,
default: null,
},
},
emits: ['close'],
methods: {
removeStash,
},
};
</script>

View File

@@ -0,0 +1,300 @@
<template>
<div
v-if="stash"
class="stash content"
>
<div class="stash-header">
<h2
:title="stash.name"
class="stash-name"
>{{ stash.name }}</h2>
<span class="header-section">
<RouterLink
v-if="stash.user"
:to="{ name: 'user', params: { username: stash.user.username } }"
class="header-item stash-username nolink"
><Icon icon="user3" /><span class="username-name">{{ stash.user.username }}</span></RouterLink>
<label
v-if="isMine"
v-tooltip="'Public'"
:class="{ public: stash.public }"
class="header-item stash-public"
>
<Icon
v-show="stash.public"
icon="eye"
/>
<Icon
v-show="!stash.public"
icon="eye-blocked"
/>
<Toggle
:checked="stash.public"
class="light"
@change="checked => publishStash(checked)"
/>
</label>
<Icon
v-if="isMine && !stash.primary"
icon="bin"
class="stash-remove"
@click.native="showRemoveStash = true"
/>
<RemoveStash
v-if="showRemoveStash"
:stash="stash"
@close="removeStash"
/>
</span>
</div>
<div class="content-inner">
<FilterBar :ranges="['scenes', 'actors', 'movies']" />
<Releases
v-if="$route.params.range === 'scenes' && stash.scenes?.length > 0"
:releases="stash.scenes.map(item => item.scene)"
:stash="stash"
class="stash-section stash-scenes"
@stash="fetchStash"
/>
<ul
v-if="$route.params.range === 'actors'"
class="stash-section stash-actors nolist"
>
<li
v-for="item in stash.actors"
:key="item.id"
><Actor
:actor="item.actor"
:stash="stash"
@stash="fetchStash"
/></li>
</ul>
<div
v-if="$route.params.range === 'movies'"
class="stash-movies"
>
<Movie
v-for="item in stash.movies"
:key="`movie-${item.id}`"
:movie="item.movie"
:stash="stash"
@stash="fetchStash"
/>
</div>
<Pagination
:items-total="totalCount"
:items-per-page="limit"
class="pagination-bottom"
/>
<Footer />
</div>
</div>
</template>
<script>
import Actor from '../actors/tile.vue';
import Releases from '../releases/releases.vue';
import Movie from '../releases/movie-tile.vue';
import RemoveStash from './remove.vue';
import Toggle from '../form/toggle.vue';
import FilterBar from '../filters/filter-bar.vue';
import Pagination from '../pagination/pagination.vue';
async function fetchStash() {
this.stash = await this.$store.dispatch('fetchStash', {
stashId: this.$route.params.stashId,
section: this.$route.params.range,
pageNumber: this.$route.params.pageNumber || 1,
limit: this.limit,
});
this.isMine = this.stash.user?.id === this.$store.state.auth.user?.id;
if (this.$route.params.range === 'scenes') {
this.totalCount = this.stash.sceneTotal;
}
if (this.$route.params.range === 'actors') {
this.totalCount = this.stash.actorTotal;
}
if (this.$route.params.range === 'movies') {
this.totalCount = this.stash.movieTotal;
}
this.pageTitle = this.stash.name;
}
async function publishStash(isPublic) {
await this.$store.dispatch('updateStash', {
stashId: this.stash.id,
stash: { public: isPublic },
});
this.fetchStash();
}
async function removeStash(removed) {
this.showRemoveStash = false;
if (removed && this.stash.user) {
this.$router.replace({ name: 'user', params: { username: this.stash.user.username } });
return;
}
if (removed) {
this.$router.replace({ name: 'home' });
}
}
async function mounted() {
await this.fetchStash();
}
export default {
components: {
Actor,
Movie,
Releases,
RemoveStash,
Pagination,
FilterBar,
Toggle,
},
data() {
return {
stash: null,
limit: Number(this.$route.query.limit) || 20,
pageTitle: null,
showRemoveStash: false,
isMine: false,
totalCount: 0,
};
},
watch: {
$route: fetchStash,
},
mounted,
methods: {
fetchStash,
publishStash,
removeStash,
},
};
</script>
<style lang="scss" scoped>
@import 'breakpoints';
.stash-header {
display: flex;
justify-content: space-between;
align-items: center;
background: var(--profile);
color: var(--text-light);
.icon {
fill: var(--text-light);
margin: -.1rem .5rem 0 0;
}
}
.header-section,
.header-item {
height: 100%;
display: flex;
align-items: center;
}
.stash-name,
.stash-username {
box-sizing: border-box;
padding: .5rem 1rem;
font-weight: bold;
}
.stash-username {
display: inline-flex;
align-items: center;
margin: 0 .5rem 0 0;
}
.stash-name {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
margin: 0;
}
.stash-public {
cursor: pointer;
margin: 0 .5rem 0 0;
.icon {
margin: 0 .75rem 0 0;
cursor: pointer;
}
}
.stash-remove.icon {
height: 100%;
padding: 0 1rem;
fill: var(--lighten-strong);
&:hover {
fill: var(--text-light);
cursor: pointer;
}
}
.stash-actors {
display: grid;
grid-gap: .5rem;
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
grid-template-rows: min-content;
flex-grow: 1;
padding: 1rem;
border-top: solid 1px var(--shadow-hint);
}
.stash-movies {
display: grid;
flex-grow: 1;
grid-template-columns: repeat(auto-fill, minmax(25rem, 1fr));
grid-template-rows: min-content;
grid-gap: 1rem;
padding: 1rem;
border-top: solid 1px var(--shadow-hint);
}
.stash-scenes .tiles {
grid-template-columns: repeat(auto-fill, minmax(22rem, 1fr));
grid-template-rows: min-content;
}
@media(max-width: $breakpoint-small) {
.stash-name {
font-size: 1.25rem;
}
.username-name {
display: none;
}
.stash-username {
margin: 0;
}
}
</style>

View File

@@ -0,0 +1,114 @@
<template>
<div class="stats">
<div class="content-inner">
<h1 class="heading">Stats</h1>
<dl class="stat-table">
<div class="stat-row">
<dt class="stat-label">Version</dt>
<dd class="stat-value">{{ version }}</dd>
</div>
<div class="stat-row">
<dt class="stat-label">Content updated</dt>
<dd class="stat-value">{{ formatDate(lastScrape, 'YYYY-MM-DD HH:mm') }}</dd>
</div>
</dl>
<dl class="stat-table">
<div class="stat-row">
<dt class="stat-label">Networks</dt>
<dd class="stat-value">{{ totalNetworks }}</dd>
</div>
<div class="stat-row">
<dt class="stat-label">Channels</dt>
<dd class="stat-value">{{ totalChannels }}</dd>
</div>
<div class="stat-row">
<dt class="stat-label">Scenes</dt>
<dd class="stat-value">{{ totalScenes }}</dd>
</div>
<div class="stat-row">
<dt class="stat-label">Movies</dt>
<dd class="stat-value">{{ totalMovies }}</dd>
</div>
<div class="stat-row">
<dt class="stat-label">Actors</dt>
<dd class="stat-value">{{ totalActors }}</dd>
</div>
</dl>
</div>
<Footer />
</div>
</template>
<script>
async function mounted() {
const stats = await this.$store.dispatch('fetchStats');
this.totalScenes = stats.totalScenes;
this.totalMovies = stats.totalMovies;
this.totalActors = stats.totalActors;
this.totalNetworks = stats.totalNetworks;
this.totalChannels = stats.totalChannels;
this.lastScrape = stats.lastScrape;
this.version = VERSION; // eslint-disable-line no-undef
}
export default {
data() {
return {
totalScenes: 0,
totalMovies: 0,
totalActors: 0,
totalNetworks: 0,
totalChannels: 0,
};
},
mounted,
};
</script>
<style lang="scss" scoped>
.stats {
display: flex;
flex-direction: column;
flex-grow: 1;
}
.content-inner {
padding: 1rem;
}
.stat-table {
margin: 0 0 1rem 0;
}
.stat-row {
width: 20rem;
max-width: 100%;
display: flex;
padding: .5rem 0;
justify-content: space-between;
&:not(:last-child) {
border-bottom: solid 1px var(--shadow-hint);
}
}
.stat-label {
display: inline-block;
font-weight: bold;
color: var(--shadow-strong);
}
.stat-value {
display: inline-block;
}
</style>

View File

@@ -1,84 +1,168 @@
<template>
<div class="photos">
<ul class="nolist photos-inner">
<li>
<a
v-if="tag.poster"
:href="`/img/${tag.poster.path}`"
:title="tag.poster.comment"
target="_blank"
rel="noopener noreferrer"
>
<img
:src="`/img/${tag.poster.thumbnail}`"
:alt="tag.poster.comment"
class="poster"
>
</a>
</li>
<div class="photos">
<Campaign
:tag="tag"
:min-height="240"
:max-ratio="1.5"
class="photo-link photo"
@campaign="campaign => $emit('campaign', campaign)"
/>
<li
v-for="photo in tag.photos"
:key="`photo-${photo.id}`"
>
<a
:title="photo.comment"
:href="`/img/${photo.path}`"
target="_blank"
rel="noopener noreferrer"
>
<img
:src="`/img/${photo.thumbnail}`"
:alt="photo.comment"
class="photo"
>
</a>
</li>
</ul>
</div>
<a
v-for="photo in photos"
:key="`photo-${photo.id}`"
:title="photo.comment"
:href="`/img/${photo.path}`"
target="_blank"
rel="noopener noreferrer"
class="photo-link"
>
<img
:src="getPath(photo, 'thumbnail', { local: true })"
:style="{ 'background-image': getBgPath(photo, 'lazy', { local: true }) }"
:alt="photo.comment"
:width="photo.thumbnailWidth"
:height="photo.thumbnailHeight"
class="photo"
@load="$emit('load', $event)"
>
<Logo :photo="photo" />
<router-link
v-if="photo.comment && photo.entity"
:to="`/${photo.entity.type}/${photo.entity.slug}`"
class="photo-comment"
>{{ photo.comment }} for {{ photo.entity.name }}</router-link>
<span
v-else-if="photo.comment"
class="photo-comment"
>{{ photo.comment }}</span>
</a>
</div>
</template>
<script>
import Logo from '../album/logo.vue';
import Campaign from '../campaigns/campaign.vue';
function photos() {
if (this.tag.poster && this.$store.state.ui.sfw) {
return [this.tag.poster].concat(this.tag.photos).map(photo => photo.sfw);
}
if (this.$store.state.ui.sfw) {
return this.tag.photos.map(photo => photo.sfw);
}
if (this.tag.poster) {
return [this.tag.poster].concat(this.tag.photos);
}
return this.tag.photos;
}
export default {
props: {
tag: {
type: Object,
default: null,
},
},
components: {
Logo,
Campaign,
},
props: {
tag: {
type: Object,
default: null,
},
},
emits: ['load', 'campaign'],
computed: {
photos,
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
.photos {
background: $profile;
display: flex;
padding: 0 1rem;
overflow: hidden;
width: 100%;
padding: .5rem 1rem;
box-sizing: border-box;
white-space: nowrap;
font-size: 0;
border-bottom: solid 1px var(--shadow-hint);
&.compact {
&::-webkit-scrollbar {
display: none;
padding: 0 1rem 1rem 1rem;
overflow-x: auto;
.photos-inner {
max-width: 100%;
display: inline-flex;
}
.poster,
.photo {
width: 100%;
margin: 0 1rem 0 0;
}
}
&.expanded {
display: flex;
justify-content: center;
flex-wrap: wrap;
padding: 0 0 0 1rem;
.photo-link {
margin: 0 .5rem .5rem 0;
}
.poster,
.photo {
max-height: 18rem;
}
}
}
.photo-link {
display: inline-block;
position: relative;
overflow: hidden;
margin: 0 .5rem 0 0;
&:last-child {
margin: 0 1rem 0 0;
}
&:hover {
.photo-comment {
transform: translateY(0);
}
::v-deep(.album-logo) {
opacity: 0;
}
}
}
.poster,
.photo {
width: 100%;
margin: 0 0 .5rem 0;
width: auto;
max-height: 15rem;
max-width: 100%;
box-shadow: 0 0 3px var(--shadow-weak);
object-fit: cover;
background-position: center;
background-size: cover;
}
.photo-comment {
width: 100%;
position: absolute;
bottom: 0;
left: 0;
box-sizing: border-box;
padding: .5rem;
color: var(--text-light);
background: var(--shadow);
font-size: .9rem;
text-shadow: 0 0 3px var(--shadow);
text-decoration: none;
white-space: normal;
line-height: 1.25;
transform: translateY(100%);
transition: transform .25s ease;
}
::v-deep(.campaign) .campaign-banner {
max-height: 15rem;
width: auto;
}
</style>

View File

@@ -1,97 +1,178 @@
<template>
<div
v-if="tag"
class="content"
>
<FilterBar :fetch-releases="fetchReleases" />
<div
v-if="tag"
class="tag content"
>
<div class="header">
<h2 class="title">
<Icon icon="price-tag4" />
{{ tag.name }}
</h2>
</div>
<div
class="tag"
:class="{ nomedia: !hasMedia }"
>
<div class="header">
<h2 class="title">
<Icon icon="price-tag4" />
{{ tag.name }}
</h2>
</div>
<div
class="content-inner"
@scroll="events.emit('scroll', $event)"
>
<div
v-if="description"
class="description header-description"
v-html="description"
/>
<div class="sidebar">
<h2 class="title">
<Icon icon="price-tag4" />
{{ tag.name }}
</h2>
<Scroll
v-if="hasMedia"
v-slot="scroll"
class="scroll-dark"
>
<Photos
:tag="tag"
:class="{ expanded }"
@load="scroll.loaded"
@campaign="campaign => showBannerCampaign = !campaign"
/>
</Scroll>
<div class="sidebar-content">
<p
v-if="description"
class="description"
v-html="description"
/>
<button
v-if="tag.photos && tag.photos.length > 2"
class="album-toggle"
@click="$router.push({ hash: '#album' })"
><Icon icon="grid3" />View album</button>
<Photos
v-if="hasMedia"
:tag="tag"
/>
</div>
</div>
<Album
v-if="showAlbum"
:items="[tag.poster, ...tag.photos]"
:title="tag.name"
:local="true"
:tag="tag"
class="portrait"
@close="$router.replace({ hash: undefined })"
/>
<div class="content-inner">
<Photos
v-if="hasMedia"
:tag="tag"
class="compact"
/>
<div
v-if="showBannerCampaign"
class="campaign-container"
>
<Campaign
:tag="tag"
:min-ratio="3"
/>
</div>
<Releases :releases="tag.releases" />
</div>
</div>
</div>
<FilterBar
ref="filter"
:fetch-releases="fetchReleases"
/>
<Releases
:releases="releases"
:done="done"
/>
<Pagination
:items-total="totalCount"
:items-per-page="limit"
class="pagination-bottom"
/>
<Footer />
</div>
</div>
</template>
<script>
/* eslint-disable no-v-html */
import { Converter } from 'showdown';
import escapeHtml from '../../../src/utils/escape-html';
import FilterBar from '../header/filter-bar.vue';
import FilterBar from '../filters/filter-bar.vue';
import Photos from './photos.vue';
import Album from '../album/album.vue';
import Releases from '../releases/releases.vue';
import Pagination from '../pagination/pagination.vue';
import Scroll from '../scroll/scroll.vue';
import Campaign from '../campaigns/campaign.vue';
const converter = new Converter();
async function fetchReleases() {
this.tag = await this.$store.dispatch('fetchTags', { tagSlug: this.$route.params.tagSlug });
async function fetchReleases(scroll = true) {
this.done = false;
this.hasMedia = this.tag.poster || this.tag.photos.length > 0;
this.description = this.tag.description && converter.makeHtml(escapeHtml(this.tag.description));
const { tag, releases, totalCount } = await this.$store.dispatch('fetchTagBySlug', {
tagSlug: this.$route.params.tagSlug,
pageNumber: Number(this.$route.params.pageNumber),
limit: this.limit,
range: this.$route.params.range,
});
this.tag = tag;
this.releases = releases;
this.totalCount = totalCount;
this.hasMedia = this.tag.poster || this.tag.photos.length > 0;
this.description = this.tag.description && converter.makeHtml(escapeHtml(this.tag.description));
this.done = true;
if (this.hasMedia) {
this.showBannerCampaign = true;
}
if (scroll && this.$refs.filter) {
this.$refs.filter.$el.scrollIntoView();
}
}
function showAlbum() {
return this.tag.photos?.length > 0 && this.$route.hash === '#album';
}
async function watchRoute(to, from) {
if (to.hash !== '#album' && from.hash !== '#album') {
await this.fetchReleases();
}
}
async function mounted() {
await this.fetchReleases();
this.pageTitle = this.tag.name;
await this.fetchReleases();
this.pageTitle = this.tag.name;
}
export default {
components: {
FilterBar,
Photos,
Releases,
},
data() {
return {
tag: null,
description: null,
releases: null,
pageTitle: null,
hasMedia: false,
};
},
mounted,
methods: {
fetchReleases,
},
components: {
FilterBar,
Releases,
Album,
Photos,
Pagination,
Scroll,
Campaign,
},
data() {
return {
tag: null,
description: null,
releases: null,
done: false,
totalCount: 0,
limit: 20,
pageTitle: null,
hasMedia: false,
expanded: false,
showBannerCampaign: false, // only show if photo campaign is not available
};
},
computed: {
showAlbum,
},
watch: {
$route: watchRoute,
'$store.state.ui.tagFilter': fetchReleases,
},
mounted,
methods: {
fetchReleases,
},
};
</script>
@@ -99,119 +180,60 @@ export default {
@import 'theme';
.description a {
color: $link;
color: var(--link);
text-decoration: inherit;
&:hover {
color: $primary;
color: var(--primary);
}
}
.description,
.description p {
padding: 0;
margin: 0;
}
</style>
<style lang="scss" scoped>
@import 'theme';
.tag {
display: flex;
flex-grow: 1;
overflow: hidden;
&.nomedia {
flex-direction: column;
.sidebar {
display: none;
}
.header {
display: flex;
}
}
}
.content-inner {
padding: 0;
}
.header {
background: $profile;
color: $text-contrast;
display: none;
padding: .5rem 1rem;
.title {
margin: 0 2rem 0 0;
}
.description {
padding: 0;
}
}
.sidebar {
background: $profile;
color: $text-contrast;
display: flex;
flex-direction: column;
width: 25rem;
box-sizing: border-box;
overflow: hidden;
.title {
padding: 1rem;
}
.description {
padding: 0 1rem;
margin: -1rem 0 0 0;
}
&.empty {
display: none;
}
}
.sidebar-content {
overflow-y: auto;
display: flex;
background: var(--profile);
color: var(--text-light);
justify-content: space-between;
}
.title {
padding: 0;
display: inline-block;
padding: .5rem 1rem;
margin: 0;
flex-shrink: 0;
text-transform: capitalize;
.icon {
fill: $text-contrast;
fill: var(--text-light);
width: 1.25rem;
height: 1.25rem;
}
}
.description {
margin: 0;
padding: 0 1rem .5rem 1rem;
line-height: 1.5;
color: var(--text-light);
background: var(--profile);
}
.releases {
padding: 1rem;
.scroll {
background: var(--background-dim);
}
@media(max-width: $breakpoint3) {
.tag {
flex-direction: column;
}
.sidebar {
display: none;
}
.header {
display: flex;
}
.photos.compact {
display: flex;
}
.campaign-container {
max-height: 90px;
padding: .5rem 1rem 0 1rem;
background: var(--background-dim);
text-align: center;
}
</style>

View File

@@ -1,147 +1,338 @@
<template>
<div class="tags">
<h3>Oral</h3>
<div class="tags">
<div class="content-inner">
<SearchBar
:placeholder="'Search tags'"
class="search"
/>
<div class="tiles">
<Tag
v-for="tag in tags.oral"
:key="`tag-${tag.id}`"
:tag="tag"
/>
</div>
<div
v-for="(tags, category) in categories"
:key="category"
class="category"
>
<h3 class="heading">{{ category }}</h3>
<h3>Penetration</h3>
<div
:key="sfw"
class="tiles"
>
<Tag
v-for="tag in tags"
:key="`tag-${tag.id}`"
:tag="tag"
:lazy="true"
/>
</div>
</div>
</div>
<div class="tiles">
<Tag
v-for="tag in tags.penetration"
:key="`tag-${tag.id}`"
:tag="tag"
/>
</div>
<h3>Group</h3>
<div class="tiles">
<Tag
v-for="tag in tags.group"
:key="`tag-${tag.id}`"
:tag="tag"
/>
</div>
<h3>Ethnicity</h3>
<div class="tiles">
<Tag
v-for="tag in tags.ethnicity"
:key="`tag-${tag.id}`"
:tag="tag"
/>
</div>
<h3>Finish</h3>
<div class="tiles">
<Tag
v-for="tag in tags.finish"
:key="`tag-${tag.id}`"
:tag="tag"
/>
</div>
<h3>Misc</h3>
<div class="tiles">
<Tag
v-for="tag in tags.misc.concat(tags.body)"
:key="`tag-${tag.id}`"
:tag="tag"
/>
</div>
</div>
<Footer />
</div>
</template>
<script>
import Tag from '../tile/tag.vue';
import Tag from './tile.vue';
import SearchBar from '../search/bar.vue';
const tagSlugsByCategory = {
popular: [
'anal',
'lesbian',
'interracial',
'mff',
'mfm',
'teen',
'milf',
'blowjob',
'gay',
'transsexual',
'dp',
'gangbang',
'facial',
'creampie',
'squirting',
],
appearance: [
'asian',
'black',
'latina',
'white',
'natural-boobs',
'enhanced-boobs',
'blonde',
'brunette',
'redhead',
'tattoos',
'piercings',
],
sexuality: [
'gay',
'bisexual',
'transsexual',
],
oral: [
'blowjob',
'pussy-eating',
'ass-eating',
'deepthroat',
'facefucking',
'69',
'atm',
],
manual: [
'handjob',
'fingering',
'anal-fingering',
'titty-fucking',
'fisting',
'anal-fisting',
],
group: [
'mfm',
'mff',
'orgy',
'gangbang',
'blowbang',
],
cumshot: [
'facial',
'creampie',
'cum-in-mouth',
'cum-on-boobs',
'cum-on-butt',
'cum-on-pussy',
'anal-creampie',
'oral-creampie',
'bukkake',
'fake-cum',
],
roleplay: [
'family',
'parody',
'schoolgirl',
'nurse',
'maid',
'nun',
],
extreme: [
'dp',
'airtight',
'dap',
'dvp',
'triple-penetration',
'tap',
'tvp',
],
fetish: [
'bdsm',
'femdom',
'bondage',
'free-use',
'latex',
'blindfold',
],
toys: [
'toys',
'toy-anal',
'toy-dp',
'double-dildo',
'double-dildo-blowjob',
'double-dildo-kiss',
'double-dildo-anal',
'double-dildo-dp',
],
misc: [
'gaping',
'squirting',
'oil',
'vr',
'bts',
],
};
function sfw() {
return this.$store.state.ui.sfw;
}
async function fetchTags() {
if (this.$route.query.query) {
await this.searchTags();
return;
}
const tags = await this.$store.dispatch('fetchTags', {
slugs: Object.values(tagSlugsByCategory).flat(),
});
const tagsBySlug = tags.reduce((acc, tag) => ({ ...acc, [tag.slug]: tag }), {});
this.categories = Object.entries(tagSlugsByCategory).reduce((acc, [category, tagSlugs]) => {
const categoryTags = tagSlugs.map((tagSlug) => tagsBySlug[tagSlug]).filter(Boolean);
if (categoryTags.length === 0) {
return acc;
}
return {
...acc,
[category]: categoryTags,
};
}, {});
}
async function searchTags() {
const tags = await this.$store.dispatch('searchTags', {
minLength: 1,
query: this.$route.query.query,
limit: 20,
});
this.categories = {
results: tags,
};
}
async function mounted() {
const tags = await this.$store.dispatch('fetchTags', {
slugs: [
'airtight',
'anal',
'anal-creampie',
'asian',
'ass-eating',
'ass-to-mouth',
'bdsm',
'blowbang',
'blowjob',
'bukkake',
'caucasian',
'creampie',
'da-tp',
'deepthroat',
'double-anal',
'double-blowjob',
'double-penetration',
'double-vaginal',
'dv-tp',
'ebony',
'facefucking',
'facial',
'gangbang',
'gaping',
'interracial',
'latina',
'mff',
'mfm',
'oral-creampie',
'orgy',
'pussy-eating',
'swallowing',
'tattoo',
'trainbang',
'triple-anal',
],
});
this.pageTitle = 'Tags';
this.tags = tags.reduce((acc, tag) => {
if (!tag.group) {
return { ...acc, misc: [...acc.misc, tag] };
}
if (acc[tag.group.slug]) {
return { ...acc, [tag.group.slug]: [...acc[tag.group.slug], tag] };
}
return { ...acc, [tag.group.slug]: [tag] };
}, { misc: [] });
await this.fetchTags();
}
export default {
components: {
Tag,
},
data() {
return {
tags: {},
};
},
mounted,
components: {
Tag,
SearchBar,
},
data() {
return {
categories: {},
pageTitle: null,
};
},
computed: {
sfw,
},
watch: {
$route: fetchTags,
'$store.state.ui.tagFilter': fetchTags,
},
mounted,
methods: {
fetchTags,
searchTags,
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
@import 'breakpoints';
.tags {
padding: 1rem;
display: flex;
flex-direction: column;
flex-grow: 1;
}
.content-inner {
padding: 1rem 1rem 0 1rem;
}
.tiles {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
grid-gap: .5rem;
grid-template-columns: repeat(auto-fill, minmax(20rem, .33fr));
grid-gap: 1rem;
}
.heading {
text-transform: capitalize;
padding: 0 0 0 .5rem;
margin: 1.25rem 0 1rem 0;
}
.category:first-child .heading {
margin: .5rem 0 1rem 0;
}
.search {
margin: 0 0 .25rem 0;
}
@media(max-width: $breakpoint-mega) {
.tiles {
grid-template-columns: repeat(auto-fill, minmax(17.75rem, .5fr));
}
::v-deep(.poster),
::v-deep(.blank) {
height: 12rem;
}
}
@media(max-width: $breakpoint-kilo) {
.tiles {
grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
grid-gap: .5rem;
}
::v-deep(.poster),
::v-deep(.blank) {
height: 14rem;
}
}
@media(max-width: $breakpoint) {
.tiles {
grid-template-columns: repeat(auto-fill, minmax(14.5rem, 1fr));
grid-gap: .5rem;
}
::v-deep(.poster),
::v-deep(.blank) {
height: 11rem;
}
}
@media(max-width: $breakpoint-small) {
::v-deep(.poster),
::v-deep(.blank) {
height: 10rem;
}
}
@media(max-width: $breakpoint-micro) {
.tiles {
grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
}
::v-deep(.poster),
::v-deep(.blank) {
height: 7.5rem;
}
}
@media(max-width: $breakpoint-mini) {
.tiles {
grid-template-columns: repeat(auto-fill, minmax(11.5rem, 1fr));
}
::v-deep(.poster),
::v-deep(.blank) {
height: 12rem;
}
}
@media(max-width: $breakpoint-nano) {
::v-deep(.poster),
::v-deep(.blank) {
height: 10rem;
}
}
@media(max-width: $breakpoint-pico) {
::v-deep(.poster),
::v-deep(.blank) {
height: 8rem;
}
}
</style>

View File

@@ -0,0 +1,166 @@
<template>
<div
class="tile"
>
<router-link
:to="`/tag/${tag.slug}`"
:title="tag.name"
:class="{ blank: !tag.poster }"
class="poster-link"
>
<template v-if="tag.poster">
<img
v-if="!lazy && !sfw"
:src="`/img/${tag.poster.thumbnail}`"
:style="{ 'background-image': `url(/img/${tag.poster.lazy})` }"
:title="comment"
:alt="tag.name"
class="poster"
>
<img
v-if="!lazy && sfw"
:src="`/img/${tag.poster.sfw.thumbnail}`"
:style="{ 'background-image': `url(/img/${tag.poster.sfw.lazy})` }"
:title="tag.poster.sfw.comment"
:alt="tag.name"
class="poster"
>
<img
v-if="lazy && !sfw"
:src="`/img/${tag.poster.thumbnail}`"
:style="{ 'background-image': `url(/img/${tag.poster.lazy})` }"
:title="comment"
:alt="tag.name"
loading="lazy"
class="poster"
>
<img
v-if="lazy && sfw"
:src="`/img/${tag.poster.sfw.thumbnail}`"
:style="{ 'background-image': `url(/img/${tag.poster.sfw.lazy})` }"
:title="tag.poster.sfw.comment"
:alt="tag.name"
loading="lazy"
class="poster"
>
<Logo
v-if="!sfw"
:photo="tag.poster"
favicon
/>
</template>
<Icon
v-else
icon="price-tag2"
/>
</router-link>
<router-link
class="title"
:to="`/tag/${tag.slug}`"
:title="tag.name"
>{{ tag.name }}</router-link>
</div>
</template>
<script>
import Logo from '../album/logo.vue';
function sfw() {
return this.$store.state.ui.sfw;
}
export default {
components: {
Logo,
},
props: {
tag: {
type: Object,
default: null,
},
lazy: {
type: Boolean,
default: false,
},
},
data() {
return {
comment: this.tag.poster?.entity ? `${this.tag.poster.comment} for ${this.tag.poster.entity.name}` : this.tag.poster?.comment,
};
},
computed: {
sfw,
},
};
</script>
<style lang="scss" scoped>
.tile {
display: flex;
flex-direction: column;
box-sizing: border-box;
position: relative;
text-decoration: none;
font-size: 0;
&:hover {
.poster,
.blank {
box-shadow: 0 0 2px var(--darken);
}
.title {
color: var(--primary);
}
}
}
.poster {
display: inline-block;
width: 100%;
object-fit: cover;
background-size: cover;
background-position: center;
box-shadow: 0 0 3px var(--darken-weak);
}
.poster,
.blank {
height: 13.5rem;
}
.poster-link {
display: flex;
align-items: center;
justify-content: center;
position: relative;
flex-grow: 1;
background: var(--shadow-hint);
.icon {
width: 2rem;
height: 2rem;
fill: var(--shadow-weak);
}
}
.title {
display: block;
box-sizing: border-box;
padding: .5rem;
overflow: hidden;
white-space: nowrap;
color: var(--shadow-strong);
text-decoration: none;
font-size: .9rem;
font-weight: bold;
text-transform: capitalize;
text-overflow: ellipsis;
}
</style>

View File

@@ -1,140 +0,0 @@
<template>
<div
v-if="actor"
class="actor"
>
<a
:href="`/actor/${actor.slug}`"
class="link"
>
<span
v-tooltip.top="actor.name"
class="name"
>{{ actor.name }}</span>
<img
v-if="actor.avatar"
:src="`/media/${actor.avatar.thumbnail || actor.avatar}`"
class="avatar"
>
<span
v-else
class="avatar"
><img
:src="`/img/avatar_${actor.gender || 'female'}.png`"
class="avatar-fallback"
></span>
<span
class="details"
>
<span>
<span
v-if="actor.age"
v-tooltip="`Born on ${formatDate(actor.birthdate, 'MMMM D, YYYY')}`"
class="age"
>{{ actor.age }}</span>
<span
v-if="actor.ageThen && actor.ageThen < actor.age"
v-tooltip="`${actor.ageThen} years old on release date`"
class="age-then"
>{{ actor.ageThen }}</span>
</span>
<span
v-if="actor.origin"
v-tooltip="`Born in ${actor.origin.country.alias || actor.origin.country.name}`"
class="country"
>
{{ actor.origin.country.alpha2 }}
<img
class="flag"
:src="`/img/flags/${actor.origin.country.alpha2.toLowerCase()}.png`"
>
</span>
</span>
</a>
</div>
</template>
<script>
export default {
props: {
actor: {
type: Object,
default: null,
},
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
.actor {
width: 100%;
background: $background;
display: inline-block;
margin: 0 .5rem .5rem 0;
position: relative;
box-shadow: 0 0 3px $shadow-weak;
}
.link {
color: $link;
text-decoration: none;
text-align: center;
&:hover {
color: $primary;
}
}
.name {
display: block;
padding: .5rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-weight: bold;
}
.avatar {
color: $shadow-weak;
background: $shadow-hint;
height: 12rem;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
object-fit: cover;
object-position: 50% 0;
}
.avatar-fallback {
max-height: 75%;
max-width: 80%;
opacity: .1;
}
.details {
background: $shadow;
color: $text-contrast;
width: 100%;
height: 1.75rem;
display: flex;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
padding: .5rem;
position: absolute;
bottom: 0;
font-size: .8rem;
font-weight: bold;
}
.age-then {
color: $highlight;
}
</style>

View File

@@ -1,65 +0,0 @@
<template>
<a
:href="`/network/${network.slug}`"
:title="network.name"
class="tile"
>
<img
:src="`/img/logos/${network.slug}/network.png`"
:alt="network.name"
class="logo"
>
</a>
</template>
<script>
export default {
props: {
network: {
type: Object,
default: null,
},
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
.tile {
background: $profile;
display: flex;
flex-direction: column;
align-items: center;
box-sizing: border-box;
padding: .5rem 1rem;
box-shadow: 0 0 3px rgba(0, 0, 0, .25);
height: 100%;
text-align: center;
}
.link {
text-decoration: none;
}
.logo {
width: 100%;
height: 5rem;
color: $text;
display: flex;
align-items: center;
justify-content: center;
object-fit: contain;
font-size: 1rem;
font-weight: bold;
filter: $logo-highlight;
}
.title {
color: $text;
height: 100%;
display: flex;
align-items: center;
margin: 0;
}
</style>

View File

@@ -1,310 +0,0 @@
<template>
<div
class="tile"
:class="{ movie: release.type === 'movie' }"
>
<span class="poster">
<span class="details">
<router-link
v-if="release.site.independent"
:to="`/network/${release.network.slug}`"
class="site site-link"
><img
:src="`/img/logos/${release.network.slug}/favicon.png`"
class="favicon"
>{{ release.network.name }}</router-link>
<router-link
v-else
v-tooltip.bottom="`Part of ${release.network.name}`"
:title="`Part of ${release.network.name}`"
:to="`/site/${release.site.slug}`"
class="site site-link"
><img
:src="`/img/logos/${release.network.slug}/favicon.png`"
class="favicon"
>{{ release.site.name }}</router-link>
<a
v-if="release.date"
v-tooltip.bottom="release.url && `View scene on ${release.site.name}`"
:title="release.url && `View scene on ${release.site.name}`"
:href="release.url"
:class="{ upcoming: isAfter(release.date, new Date()) }"
target="_blank"
rel="noopener noreferrer"
class="date"
>{{ formatDate(release.date, 'MMM D, YYYY') }}</a>
<a
v-else
:href="release.url"
:class="{ upcoming: isAfter(release.date, new Date()) }"
title="Scene date N/A, showing date added"
target="_blank"
rel="noopener noreferrer"
class="date"
>{{ `(${formatDate(release.dateAdded, 'MMM D, YYYY')})` }}</a>
</span>
<a
:href="`/${release.type || 'scene'}/${release.id}`"
target="_blank"
rel="noopener noreferrer"
class="link"
>
<img
v-if="release.poster"
:src="`/media/${release.poster.thumbnail}`"
:alt="release.title"
class="thumbnail"
>
<img
v-else-if="release.covers && release.covers.length > 0"
:src="`/media/${release.covers[0].thumbnail}`"
:alt="release.title"
class="thumbnail"
>
<div
v-else
:title="release.title"
class="thumbnail"
>No thumbnail available</div>
</a>
</span>
<div class="info">
<a
:href="`/${release.type || 'scene'}/${release.id}`"
target="_blank"
rel="noopener noreferrer"
class="row link"
>
<h3
v-tooltip.top="release.title"
:title="release.title"
class="title"
>
<Icon
v-if="release.type === 'movie'"
icon="film"
/>{{ release.title }}
</h3>
</a>
<span class="row">
<ul class="actors nolist">
<li
v-for="actor in release.actors"
:key="actor.id"
class="actor"
>
<a
:href="`/actor/${actor.slug}`"
class="actor-link"
>{{ actor.name }}</a>
</li>
</ul>
</span>
<ul
:title="release.tags.map(tag => tag.name).join(', ')"
class="tags nolist"
>
<li
v-for="tag in release.tags"
:key="`tag-${tag.slug}`"
class="tag"
>
<router-link
:to="`/tag/${tag.slug}`"
class="tag-link"
>{{ tag.name }}</router-link>
</li>
</ul>
</div>
</div>
</template>
<script>
export default {
props: {
release: {
type: Object,
default: null,
},
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
.tile {
background: $background;
display: flex;
flex-direction: column;
box-sizing: border-box;
padding: 0 0 .5rem 0;
overflow: hidden;
box-shadow: 0 0 3px $shadow-weak;
height: 100%;
}
.poster {
position: relative;
margin: 0 0 .5rem 0;
}
.thumbnail {
width: 100%;
height: 14rem;
display: flex;
justify-content: center;
align-items: center;
object-fit: cover;
background-position: center;
background-size: cover;
background-color: $shadow-hint;
color: $shadow;
text-shadow: 1px 1px 0 $highlight;
}
.row {
display: flex;
justify-content: space-between;
align-items: center;
box-sizing: border-box;
padding: 0 .5rem;
margin: 0 0 .25rem 0;
}
.details {
width: 100%;
display: flex;
justify-content: space-between;
position: absolute;
font-size: 0;
.favicon {
height: 1rem;
margin: 0 .25rem 0 0;
}
}
.site,
.date {
color: #fff;
display: flex;
align-items: center;
background: $shadow;
position: relative;
font-size: .8rem;
padding: .25rem;
text-decoration: none;
}
.date.upcoming:before {
content: '';
color: $text-contrast;
background: $primary;
width: .25rem;
display: inline-block;
position: absolute;
top: 0;
bottom: 0;
left: -.75rem;
padding: .25rem;
font-size: .8rem;
font-weight: bold;
}
.site {
font-weight: bold;
}
.info {
display: flex;
flex-direction: column;
flex-grow: 1;
}
.link {
text-decoration: none;
}
.title {
margin: 0 .25rem .25rem 0;
color: $text;
max-height: 2.75rem;
font-size: 1rem;
line-height: 1.5;
text-overflow: ellipsis;
overflow: hidden;
.icon {
margin: 0 .25rem 0 0;
}
}
.network {
color: #555;
margin: 0 .25rem 0 0;
font-size: .8rem;
}
.actors {
word-wrap: break-word;
overflow: hidden;
max-height: 2.75rem;
line-height: 1.5rem;
}
.tags {
max-height: .5rem;
padding: .25rem .5rem 1rem .5rem;
word-wrap: break-word;
overflow-y: hidden;
}
.actor {
margin: 0 .25rem 0 0;
}
.tag {
margin: 0 .25rem .25rem 0;
}
.actor:not(:last-of-type)::after {
content: ",";
}
.actor-link {
text-decoration: none;
&:hover {
color: $primary;
}
}
.actor-link {
color: $link;
}
.tag-link {
color: $shadow;
display: inline-block;
padding: .25rem;
font-size: .75rem;
font-weight: bold;
text-decoration: none;
line-height: 1;
border: solid 1px $shadow-hint;
&:hover {
color: $primary;
}
}
</style>

View File

@@ -1,66 +0,0 @@
<template>
<a
:href="`/site/${site.slug}`"
:title="site.name"
class="tile"
>
<img
:src="`/img/logos/${site.network.slug}/${site.slug}.png`"
:alt="site.name"
class="logo"
>
</a>
</template>
<script>
export default {
props: {
site: {
type: Object,
default: null,
},
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
.tile {
background: $tile;
display: flex;
flex-direction: column;
align-items: center;
box-sizing: border-box;
padding: .5rem 1rem;
border-radius: .25rem;
box-shadow: 0 0 3px rgba(0, 0, 0, .25);
height: 100%;
text-align: center;
}
.link {
text-decoration: none;
}
.logo {
width: 100%;
height: 5rem;
color: $text;
display: flex;
align-items: center;
justify-content: center;
object-fit: contain;
font-size: 1rem;
font-weight: bold;
filter: $logo-highlight;
}
.title {
color: $text;
height: 100%;
display: flex;
align-items: center;
margin: 0;
}
</style>

View File

@@ -1,60 +0,0 @@
<template>
<a
:href="`/tag/${tag.slug}`"
:title="tag.name"
class="tile"
>
<img
v-if="tag.poster"
:src="`/img/${tag.poster.thumbnail}`"
:alt="tag.name"
class="poster"
>
<span class="title">{{ tag.name }}</span>
</a>
</template>
<script>
export default {
props: {
tag: {
type: Object,
default: null,
},
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
.tile {
color: $text;
background: $background;
display: flex;
flex-direction: column;
align-items: center;
box-sizing: border-box;
box-shadow: 0 0 3px rgba(0, 0, 0, .25);
text-align: center;
text-decoration: none;
}
.poster {
width: 100%;
height: 14rem;
object-fit: cover;
}
.title {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1rem;
padding: .5rem 1rem;
font-weight: bold;
text-transform: capitalize;
}
</style>

View File

@@ -0,0 +1,184 @@
<template>
<div class="tooltip-container">
<div
ref="trigger"
class="trigger noselect"
@click.stop="toggle"
>
<slot />
</div>
<teleport to="body">
<div
v-if="opened"
ref="tooltip"
class="tooltip-wrapper"
:style="{ transform: `translate3d(${tooltipX}px, ${tooltipY}px, 0)` }"
@click.stop
>
<div
class="tooltip-inner"
:style="{ 'max-height': `calc(100vh - ${tooltipY}px - 1rem)` }"
>
<div class="tooltip">
<slot name="tooltip" />
</div>
</div>
<div
class="tooltip-arrow"
:style="{ transform: `translate3d(${arrowOffset}px, 0, 0)` }"
/>
</div>
</teleport>
</div>
</template>
<script>
import { nextTick } from 'vue';
function getX(triggerBoundary, tooltipBoundary) {
const idealPosition = triggerBoundary.left + (triggerBoundary.width / 2) - (tooltipBoundary.width / 2);
const rightEdgeOverflow = Math.max((idealPosition + tooltipBoundary.width) - window.innerWidth, 0);
// don't overflow left edge
if (idealPosition < 0) {
return {
tooltipX: 0,
arrowOffset: idealPosition,
};
}
// don't overflow right edge
if (rightEdgeOverflow > 0) {
return {
tooltipX: window.innerWidth - tooltipBoundary.width,
arrowOffset: rightEdgeOverflow,
};
}
// position at the center of trigger
return {
tooltipX: idealPosition,
arrowOffset: 0,
};
}
async function calculate() {
if (!this.opened) {
return;
}
const triggerBoundary = this.$refs.trigger.getBoundingClientRect();
const tooltipBoundary = this.$refs.tooltip.getBoundingClientRect();
const { tooltipX, arrowOffset } = this.getX(triggerBoundary, tooltipBoundary);
this.tooltipY = triggerBoundary.top + triggerBoundary.height + 5;
this.tooltipX = tooltipX;
this.arrowOffset = arrowOffset;
}
async function open() {
this.events.emit('blur');
await nextTick();
this.opened = true;
await nextTick();
this.calculate();
this.$emit('open');
}
function close() {
this.opened = false;
this.tooltipY = 0;
this.tooltipX = 0;
this.arrowOffset = 0;
this.$emit('close');
}
function toggle() {
if (this.opened) {
this.close();
return;
}
this.open();
}
function mounted() {
this.events.on('blur', () => {
this.close();
});
this.events.on('resize', () => {
this.calculate();
});
this.events.on('scroll', () => {
this.calculate();
});
}
export default {
data() {
return {
opened: false,
tooltipX: 0,
tooltipY: 0,
arrowOffset: 0,
};
},
emits: ['open', 'close'],
mounted,
methods: {
calculate,
getX,
open,
close,
toggle,
},
};
</script>
<style lang="scss" scoped>
.tooltip-wrapper {
display: flex;
top: 0;
left: 0;
flex-direction: column;
justify-content: center;
position: absolute;
z-index: 10;
}
.tooltip-inner {
position: relative;
overflow-y: auto;
background: var(--background);
box-shadow: 0 0 .5rem var(--darken);
}
.tooltip {
position: relative;
background: var(--background);
}
.tooltip-arrow {
content: '';
width: 0;
height: 0;
position: absolute;
top: -.5rem;
left: calc(50% - .5rem);
border-left: .5rem solid transparent;
border-right: .5rem solid transparent;
border-bottom: .5rem solid var(--background);
margin: 0 auto;
filter: drop-shadow(0 0 3px var(--darken-weak));
}
</style>

View File

@@ -0,0 +1,252 @@
<template>
<div class="alert">
<div class="alert-section alert-header">
<div class="alert-targets">
<Icon
v-if="alert.notify"
icon="bell2"
class="alert-action"
/>
<Icon
v-if="alert.email"
icon="envelop"
class="alert-action"
/>
<Icon
v-if="alert.stashes?.length > 0"
v-tooltip="alert.stashes.map(stash => stash.name).join()"
icon="heart7"
class="alert-action"
/>
</div>
<span class="header-actions noselect">
<Icon
v-if="isMe"
icon="bin"
class="alert-remove"
@click.native="showRemoveAlert = true"
/>
<RemoveAlert
v-if="showRemoveAlert"
:alert="alert"
@close="removeAlert"
/>
</span>
</div>
<div class="alert-triggers">
<div
v-if="alert.actors?.length > 0"
class="alert-section alert-trigger"
>
<h4
v-if="alert.actors.length > 1"
class="alert-heading"
>Actors</h4>
<h4
v-else
class="alert-heading"
>Actor</h4>
<ul class="alert-actors nolist">
<li
v-for="actor in alert.actors"
:key="`actor-${actor.id}`"
class="alert-actor"
>
<ActorPreview
:actor="actor"
:alert="alert"
/>
</li>
</ul>
</div>
<div
v-if="alert.tags?.length > 0"
class="alert-section alert-trigger"
>
<h4
v-if="alert.tags.length > 1"
class="alert-heading"
>Tags</h4>
<h4
v-else
class="alert-heading"
>Tag</h4>
<ul class="alert-tags nolist">
<li
v-for="tag in alert.tags"
:key="`tag-${tag.id}`"
><router-link
:to="`/tag/${tag.slug}`"
class="tag nolink"
>{{ tag.name }}</router-link></li>
</ul>
</div>
<div
v-if="alert.entity"
class="alert-section alert-trigger"
>
<h4 class="alert-heading">Channel</h4>
<Entity
v-if="alert.entity"
:entity="alert.entity"
class="entity"
/>
</div>
</div>
</div>
</template>
<script>
import ActorPreview from '../actors/preview.vue';
import RemoveAlert from '../alerts/remove.vue';
import Entity from '../entities/tile.vue';
async function removeAlert(removed) {
this.showRemoveAlert = false;
if (removed) {
this.$emit('remove');
}
}
export default {
components: {
ActorPreview,
Entity,
RemoveAlert,
},
props: {
alert: {
type: Object,
default: null,
},
isMe: {
type: Boolean,
default: false,
},
},
emits: ['remove'],
data() {
return {
showRemoveAlert: false,
};
},
methods: {
removeAlert,
},
};
</script>
<style lang="scss" scoped>
.alert {
min-width: 0;
height: 100%;
background: var(--background);
box-shadow: 0 0 3px var(--darken-weak);
}
.alert-header {
border-bottom: solid 1px var(--shadow-hint);
}
.alert-section {
display: inline-block;
padding: .5rem;
}
.alert-header {
display: flex;
justify-content: space-between;
align-items: stretch;
padding: 0;
.alert-action {
padding: .5rem;
fill: var(--shadow);
}
}
.alert-triggers {
display: flex;
}
.alert-heading {
display: block;
padding: 0 .5rem .5rem 0;
margin: 0;
color: var(--shadow-strong);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: .9rem;
}
.alert-more {
flex-shrink: 0;
margin: 0 0 0 .5rem;
color: var(--shadow);
font-size: .9rem;
}
.header-actions {
display: flex;
align-items: stretch;
}
.alert-remove {
height: auto;
padding: 0 .5rem 0 .75rem;
fill: var(--shadow);
&:hover {
cursor: pointer;
fill: var(--shadow-strong);
}
}
.alert-triggers {
dislay: flex;
flex-wrap: wrap;
}
.alert-trigger {
flex-shrink: 0;
flex-grow: 1;
}
.alert-actors,
.alert-tags {
display: flex;
grid-gap: .5rem;
}
.tag {
color: var(--shadow-strong);
padding: .5rem;
border: solid 1px var(--shadow-hint);
font-size: .9rem;
font-weight: bold;
&:hover {
cursor: pointer;
border: solid 1px var(--primary);
}
}
.entity {
width: 10rem;
height: 2.5rem;
}
</style>

View File

@@ -0,0 +1,247 @@
<template>
<div class="stash">
<div class="stash-section stash-header">
<router-link
:to="{ name: 'stash', params: { stashId: stash.id, stashSlug: stash.slug, range: 'scenes', pageNumber: 1 } }"
class="stash-link nolink"
>
<h4 class="stash-name">{{ stash.name }}</h4>
<span class="stash-more">Browse</span>
</router-link>
<span class="header-actions noselect">
<label
v-if="isMe"
v-tooltip="'Public'"
:class="{ public: stash.public }"
class="stash-public"
>
<Icon
v-show="stash.public"
icon="eye"
/>
<Icon
v-show="!stash.public"
icon="eye-blocked"
/>
<Toggle
:checked="stash.public"
@change="checked => publishStash(checked)"
/>
</label>
<Icon
v-if="isMe && !stash.primary"
icon="bin"
class="stash-remove"
@click.native="showRemoveStash = true"
/>
<RemoveStash
v-if="showRemoveStash"
:stash="stash"
@close="removeStash"
/>
</span>
</div>
<ul
v-if="stash.scenes?.length > 0"
class="stash-section stash-scenes nolist"
>
<li
v-for="{ scene } in stash.scenes"
:key="scene.id"
class="stash-scene"
>
<ScenePreview
:scene="scene"
:stash="stash"
/>
</li>
</ul>
<ul
v-if="stash.actors?.length > 0"
class="stash-section stash-actors nolist"
>
<li
v-for="{ actor } in stash.actors"
:key="actor.id"
class="stash-actor"
>
<ActorPreview
:actor="actor"
:stash="stash"
/>
</li>
</ul>
</div>
</template>
<script>
import ActorPreview from '../actors/preview.vue';
import ScenePreview from '../releases/scene-preview.vue';
import RemoveStash from '../stashes/remove.vue';
import Toggle from '../form/toggle.vue';
async function publishStash(isPublic) {
await this.$store.dispatch('updateStash', {
stashId: this.stash.id,
stash: { public: isPublic },
});
this.$emit('publish', isPublic);
}
async function removeStash(removed) {
this.showRemoveStash = false;
if (removed) {
this.$emit('remove');
}
}
export default {
components: {
ActorPreview,
ScenePreview,
RemoveStash,
Toggle,
},
props: {
stash: {
type: Object,
default: null,
},
isMe: {
type: Boolean,
default: false,
},
},
emits: ['publish', 'remove'],
data() {
return {
showRemoveStash: false,
};
},
methods: {
publishStash,
removeStash,
},
};
</script>
<style lang="scss" scoped>
.stash {
min-width: 0;
height: 100%;
background: var(--background);
box-shadow: 0 0 3px var(--darken-weak);
}
.stash-section {
display: flex;
align-items: center;
padding: .5rem;
&:not(:last-child),
&.stash-header {
border-bottom: solid 1px var(--shadow-hint);
}
}
.stash-header {
justify-content: space-between;
align-items: stretch;
padding: 0;
}
.stash-link {
display: inline-flex;
align-items: center;
flex-grow: 1;
text-decoration: none;
overflow: hidden;
&:hover .stash-more {
color: var(--primary);
}
}
.stash-name {
display: inline-block;
padding: .5rem;
margin: 0;
color: var(--shadow-strong);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.stash-more {
flex-shrink: 0;
margin: 0 0 0 .5rem;
color: var(--shadow);
font-size: .9rem;
}
.header-actions {
display: flex;
align-items: stretch;
}
.stash-public {
display: inline-flex;
align-items: center;
padding: .5rem;
cursor: pointer;
.icon {
fill: var(--shadow-strong);
margin: 0 .5rem 0 0;
}
}
.stash-remove {
height: auto;
padding: 0 .5rem 0 .75rem;
fill: var(--shadow);
&:hover {
cursor: pointer;
fill: var(--shadow-strong);
}
}
.stash-actors,
.stash-scenes {
display: flex;
overflow-x: auto;
grid-gap: .5rem;
scroll-behavior: smooth;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
.stash-scenes {
height: 8rem;
}
.stash-actor,
.stash-scene {
height: 100%;
flex-shrink: 0;
font-size: 0;
&:last-child {
padding: 0 .5rem 0 0;
}
}
</style>

View File

@@ -0,0 +1,250 @@
<template>
<div
v-if="user"
class="user"
>
<div class="header">
<h2 class="username">{{ user.username }}</h2>
</div>
<section
v-if="user.stashes?.length > 0"
class="section"
>
<div class="section-header">
<h3 class="section-heading">Stashes</h3>
<Icon
icon="plus3"
class="header-add"
@click="showAddStash = true"
/>
</div>
<ul class="section-body stashes nolist">
<li
v-for="stash in user.stashes"
:key="stash.id"
class="stashes-stash"
>
<Stash
:stash="stash"
:is-me="isMe"
@publish="() => fetchUser()"
@remove="() => fetchUser()"
/>
</li>
<li
v-if="isMe"
class="stashes-stash stashes-add"
@click="showAddStash = true"
>
<Icon icon="plus2" />
</li>
</ul>
<AddStash
v-if="showAddStash"
@close="closeAddStash"
/>
</section>
<section class="section">
<div class="section-header">
<h3 class="section-heading">Alerts</h3>
<Icon
icon="plus3"
class="header-add"
@click="showAddAlert = true"
/>
</div>
<ul class="section-body alerts nolist">
<li
v-for="alert in user.alerts"
:key="`alert-${alert.id}`"
class="alert"
>
<Alert
:alert="alert"
:is-me="isMe"
@remove="() => fetchUser()"
/>
</li>
<li
class="alerts-add"
@click="showAddAlert = true"
>
<Icon icon="plus2" />
</li>
</ul>
<AddAlert
v-if="showAddAlert"
@close="closeAddAlert"
>Alert</AddAlert>
</section>
</div>
</template>
<script>
import Stash from './stash.vue';
import Alert from './alert.vue';
import AddStash from '../stashes/add.vue';
import AddAlert from '../alerts/add.vue';
async function fetchUser() {
this.user = await this.$store.dispatch('fetchUser', this.$route.params.username);
this.isMe = this.user?.id === this.$store.state.auth.user?.id;
this.pageTitle = this.user?.username;
}
async function closeAddStash(addedStash) {
this.showAddStash = false;
if (addedStash) {
await this.fetchUser();
}
}
async function closeAddAlert(addedAlert) {
this.showAddAlert = false;
if (addedAlert) {
await this.fetchUser();
}
}
async function mounted() {
await this.fetchUser();
}
export default {
components: {
AddAlert,
AddStash,
Alert,
Stash,
},
data() {
return {
user: this.$route.params.username === this.$store.state.auth.user?.username
? this.$store.state.auth.user
: null,
isMe: false,
pageTitle: null,
showAddStash: false,
showAddAlert: false,
};
},
mounted,
methods: {
closeAddAlert,
closeAddStash,
fetchUser,
},
};
</script>
<style lang="scss" scoped>
@import 'breakpoints';
.header {
display: flex;
justify-content: space-between;
background: var(--profile);
}
.username {
padding: .5rem 1rem;
margin: 0;
font-size: 1.5rem;
color: var(--text-light);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.section {
padding: 1rem 0;
margin: 0 0 1rem 0;
}
.stashes,
.alerts {
display: grid;
grid-template-columns: 1fr 1fr;
grid-auto-rows: 15fr;
grid-gap: 1rem;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin: 0 0 1rem 0;
}
.section-body {
padding: 0 1rem;
}
.section-heading {
color: var(--primary);
padding: 0 1rem;
margin: 0;
font-size: 1.25rem;
}
.header-add {
height: auto;
padding: .5rem 1rem;
fill: var(--shadow);
&:hover {
fill: var(--primary);
cursor: pointer;
}
}
.stashes-stash {
min-width: 0;
}
.stashes-add,
.alerts-add {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
padding: 1rem;
background: var(--shadow-touch);
.icon {
width: 1.5rem;
height: 1.5rem;
fill: var(--shadow-hint);
}
&:hover {
background: var(--shadow-hint);
cursor: pointer;
.icon {
fill: var(--shadow-weak);
}
}
}
@media(max-width: $breakpoint-kilo) {
.stashes,
.alerts {
grid-template-columns: 1fr;
}
}
</style>

View File

@@ -0,0 +1,143 @@
<template>
<video
ref="player"
:poster="poster"
crossorigin="anonymous"
class="player video-js vjs-big-play-centered"
@playing="$emit('play')"
@pause="$emit('pause')"
/>
</template>
<script>
import videoJs from 'video.js';
import 'videojs-vr/dist/videojs-vr.min';
function updatePlayer() {
this.player.src(this.getPath(this.video));
this.player.poster(this.poster);
}
function initPlayer() {
this.player = videoJs(this.$refs.player, {
controls: true,
inactivityTimeout: 1000,
controlBar: {
pictureInPictureToggle: false,
volumePanel: {
inline: false,
},
},
}, () => {
this.player.src(this.getPath(this.video));
if (this.video.isVr) {
this.player.vr({ projection: '180' });
}
});
}
export default {
props: {
video: {
type: Object,
default: null,
},
poster: {
type: String,
default: null,
},
},
data() {
return {
player: null,
};
},
emits: ['play', 'pause'],
watch: {
video: updatePlayer,
poster: updatePlayer,
},
mounted: initPlayer,
};
</script>
<style lang="scss">
.player.video-js {
.vjs-poster {
background-size: cover;
}
.vjs-control-bar {
background-color: var(--darken);
}
.vjs-slider {
background-color: var(--darken-weak);
}
.vjs-play-progress {
background-color: var(--primary);
.vjs-time-tooltip {
color: var(--darken-strong);
}
}
.vjs-load-progress {
background-color: var(--lighten-weak);
div {
background-color: var(--darken-weak);
}
}
.vjs-mouse-display .vjs-time-tooltip {
background-color: var(--darken-strong);
}
.vjs-remaining-time {
line-height: 2rem;
font-size: .75rem;
font-weight: bold;
}
.vjs-big-play-button {
background-color: var(--darken);
width: 4rem;
height: 4rem;
margin: -2rem 0 0 -2rem;
border: none;
border-radius: 50%;
.vjs-icon-placeholder {
font-size: 2.5rem;
line-height: 4rem;
}
&:hover {
background-color: var(--primary);
}
}
.vjs-control:focus::before,
.vjs-control:hover::before,
.vjs-control:focus {
text-shadow: 0 0 .5rem var(--lighten-strong);
}
.vjs-error-display {
overflow: hidden;
}
.vjs-modal-dialog-content {
white-space: normal;
}
&:hover {
.vjs-big-play-button {
background-color: var(--primary);
}
}
}
</style>

View File

@@ -0,0 +1,9 @@
$breakpoint-pico: 270px;
$breakpoint-nano: 320px;
$breakpoint-mini: 400px;
$breakpoint-micro: 540px;
$breakpoint-small: 620px;
$breakpoint: 720px;
$breakpoint-kilo: 900px;
$breakpoint-mega: 1200px;
$breakpoint-giga: 1500px;

5
assets/css/_forms.scss Normal file
View File

@@ -0,0 +1,5 @@
.form-heading {
margin: 0 0 .5rem 0;
font-size: 1rem;
font-weight: bold;
}

85
assets/css/_inputs.scss Normal file
View File

@@ -0,0 +1,85 @@
.input {
box-sizing: border-box;
padding: .5rem;
border: solid 1px var(--shadow-hint);
color: var(--shadow-strong);
background: var(--background);
font-size: 1rem;
font-family: inherit;
&:focus {
border: solid 1px var(--primary);
}
&::-webkit-calendar-picker-indicator {
opacity: .5;
}
}
.select {
color: var(--shadow-strong);
background: var(--background);
padding: .5rem;
font-size: 1rem;
border: solid 1px var(--shadow-weak);
cursor: pointer;
}
.button {
border: none;
background: none;
padding: .5rem;
font-size: .9rem;
font-weight: bold;
&:hover {
cursor: pointer;
}
}
.button-primary {
color: var(--text-light);
background: var(--primary);
&:hover {
background: var(--primary-strong);
}
&:disabled {
background: var(--shadow-weak);
cursor: default;
}
}
.button-secondary {
color: var(--primary);
&:hover {
color: var(--highlight-strong);
background: var(--primary);
}
}
.album-toggle {
height: fit-content;
display: inline-flex;
align-items: center;
justify-content: center;
padding: .5rem 1rem;
border: none;
border-bottom: solid 1px var(--shadow-hint);
color: var(--shadow);
background: var(--background-dim);
font-size: 1rem;
font-weight: bold;
.icon {
fill: var(--shadow);
margin: -.1rem .5rem 0 0;
}
&:hover {
background: var(--shadow-hint);
cursor: pointer;
}
}

View File

@@ -17,3 +17,17 @@
margin: 0;
}
}
.nolink {
display: inline-block;
color: inherit;
text-decoration: none;
}
:focus {
outline: none;
}
::-moz-focus-inner {
border: 0;
}

View File

@@ -1,37 +1,132 @@
/* $primary: #ff886c; */
$breakpoint0: 540px;
$breakpoint: 720px;
$breakpoint2: 900px;
$breakpoint3: 1200px;
$breakpoint4: 1500px;
$primary: #ff6c88;
$background: #fff;
$background-dim: #fafafa;
$text: #222;
$text-contrast: #fff;
:root {
--primary: #f65596;
--primary-strong: #f90071;
--primary-faded: #ffcce4;
$shadow: rgba(0, 0, 0, .5);
$shadow-extreme: rgba(0, 0, 0, .9);
$shadow-strong: rgba(0, 0, 0, .7);
$shadow-weak: rgba(0, 0, 0, .2);
$shadow-hint: rgba(0, 0, 0, .1);
/*
--primary: #f04288;
$highlight: rgba(255, 255, 255, .5);
$highlight-extreme: rgba(255, 255, 255, .9);
$highlight-strong: rgba(255, 255, 255, .7);
$highlight-weak: rgba(255, 255, 255, .2);
$highlight-hint: rgba(255, 255, 255, .075);
--primary: #ff6c88;
--primary-strong: #ff4166;
--primary-faded: #ffdfee;
$logo-shadow: drop-shadow(1px 0 0 $shadow-weak) drop-shadow(-1px 0 0 $shadow-weak) drop-shadow(0 1px 0 $shadow-weak) drop-shadow(0 -1px 0 $shadow-weak);
/* $logo-highlight: drop-shadow(1px 0 0 $highlight-weak) drop-shadow(-1px 0 0 $highlight-weak) drop-shadow(0 1px 0 $highlight-weak) drop-shadow(0 -1px 0 $highlight-weak); */
$logo-highlight: drop-shadow(0 0 1px $highlight);
--primary: #f28;
--primary-strong: #f90071;
--primary-faded: #ffcce4;
*/
$profile: #222;
$tile: #2a2a2a;
--text-dark: #222;
--text-light: #fff;
$link: #cc4466;
$empty: #333;
--background-light: #fff;
--background-dark: #222;
$male: #0af;
$female: #f0a;
--darken: rgba(0, 0, 0, .5);
--darken-censor: rgba(0, 0, 0, .95);
--darken-extreme: rgba(0, 0, 0, .9);
--darken-strong: rgba(0, 0, 0, .7);
--darken-weak: rgba(0, 0, 0, .2);
--darken-hint: rgba(0, 0, 0, .1);
--darken-touch: rgba(0, 0, 0, .05);
--lighten-censor: rgba(255, 255, 255, .95);
--lighten-extreme: rgba(255, 255, 255, .9);
--lighten-strong: rgba(255, 255, 255, .7);
--lighten: rgba(255, 255, 255, .5);
--lighten-weak: rgba(255, 255, 255, .2);
--lighten-hint: rgba(255, 255, 255, .05);
--lighten-touch: rgba(255, 255, 255, .03);
--logo-shadow: drop-shadow(1px 0 0 $shadow-weak) drop-shadow(-1px 0 0 $shadow-weak) drop-shadow(0 1px 0 $shadow-weak) drop-shadow(0 -1px 0 $shadow-weak);
--logo-highlight: drop-shadow(0 0 1px $highlight);
--info: #321b24;
--male: #0af;
--female: #f0a;
--alert: #f00;
--error: #f00;
--warn: #fa0;
--success: #5c2;
--enabled: #5c2;
--enabled-background: rgba(0, 255, 0, .1);
--disabled: #c20;
--disabled-background: rgba(255, 0, 0, .1);
--disabled-handle: #aaa;
}
.light {
--text: #222;
--text-contrast: #fff;
--background: var(--background-light);
--background-censor: rgba(255, 255, 255, .95);
--background-dim: #f5f5f5;
--background-soft: #fdfdfd;
--profile: #222;
--tile: #2a2a2a;
--link: #dd6688;
--link-external: #48f;
--empty: #333;
--crease: #eaeaea;
--shadow: rgba(0, 0, 0, .5);
--shadow-censor: rgba(0, 0, 0, .95);
--shadow-extreme: rgba(0, 0, 0, .9);
--shadow-strong: rgba(0, 0, 0, .7);
--shadow-modest: rgba(0, 0, 0, .3);
--shadow-weak: rgba(0, 0, 0, .2);
--shadow-hint: rgba(0, 0, 0, .1);
--shadow-touch: rgba(0, 0, 0, .05);
--highlight: rgba(255, 255, 255, .5);
--highlight-extreme: rgba(255, 255, 255, .9);
--highlight-strong: rgba(255, 255, 255, .7);
--highlight-modest: rgba(255, 255, 255, .3);
--highlight-weak: rgba(255, 255, 255, .2);
--highlight-hint: rgba(255, 255, 255, .075);
}
.dark {
--text: #fff;
--text-contrast: #222;
--background: #181818;
--background-censor: rgba(0, 0, 0, .95);
--background-dim: #111;
--background-soft: #000;
--profile: #0a0a0a;
--tile: #2a2a2a;
--link: #dd6688;
--empty: #333;
--crease: #222;
--shadow: rgba(255, 255, 255, .5);
--shadow-extreme: rgba(255, 255, 255, .9);
--shadow-strong: rgba(255, 255, 255, .7);
--shadow-modest: rgba(255, 255, 255, .3);
--shadow-weak: rgba(255, 255, 255, .2);
--shadow-hint: rgba(255, 255, 255, .075);
--shadow-touch: rgba(255, 255, 255, .05);
--highlight: rgba(0, 0, 0, .5);
--highlight-extreme: rgba(0, 0, 0, .9);
--highlight-strong: rgba(0, 0, 0, .7);
--highlight-modest: rgba(0, 0, 0, .3);
--highlight-weak: rgba(0, 0, 0, .2);
--highlight-hint: rgba(0, 0, 0, .1);
}

View File

@@ -7,7 +7,10 @@
background: #222;
color: white;
border-radius: 16px;
padding: 5px 10px 4px;
}
&:not(.popover) .tooltip-inner {
padding: .5rem 1rem;
}
.tooltip-arrow {
@@ -81,12 +84,11 @@
}
&.popover {
$color: #f9f9f9;
$color: #fff;
.popover-inner {
background: $color;
color: black;
padding: 24px;
border-radius: 5px;
box-shadow: 0 5px 30px rgba(black, .1);
}

1663
assets/css/_video.scss Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,8 @@
@import 'theme';
@import 'forms';
@import 'inputs';
@import 'states';
@import 'video';
@import 'tooltip';
html,
@@ -8,9 +11,13 @@ body {
}
body {
color: $text;
margin: 0;
font-family: Verdana, sans-serif;
font-family: Arial, Helvetica, sans-serif;
overflow: hidden;
}
#container {
height: 100%;
}
.nolist {
@@ -24,69 +31,25 @@ body {
}
.heading {
color: $shadow;
color: var(--shadow);
padding: 0;
margin: 0 0 1rem 0;
font-size: 1.5rem;
}
.icon.icon-href {
fill: $shadow;
fill: var(--shadow);
:hover {
fill: $primary;
fill: var(--primary);
}
}
.expand {
display: flex;
justify-content: center;
align-items: center;
padding: .5rem 0;
font-weight: bold;
font-size: .9rem;
cursor: pointer;
.icon {
fill: $shadow;
}
&:hover {
background: $shadow-hint;
.icon {
fill: $shadow-strong;
}
}
.link {
color: var(--link);
text-decoration: none;
}
.expand-sidebar:hover {
background: $shadow-hint;
}
.expand-header {
display: none;
&:hover {
background: $shadow-hint;
}
}
.collapse-header {
width: 100%;
justify-content: center;
align-items: center;
padding: 0;
background: $profile;
.icon {
width: 100%;
fill: $highlight;
padding: .5rem 0;
}
&:hover .icon {
background: $highlight-hint;
fill: $text-contrast;
}
.link-external {
color: var(--link-external);
}

View File

@@ -0,0 +1,235 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="svg2"
width="338.93176"
height="589.84552"
viewBox="0 0 338.93176 589.84552"
sodipodi:docname="Ace Rockwood - outline - hardon.svg"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)">
<metadata
id="metadata8">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs6" />
<sodipodi:namedview
inkscape:document-rotation="0"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1024"
id="namedview4"
showgrid="false"
inkscape:zoom="1.1565452"
inkscape:cx="4.7736842"
inkscape:cy="356.99934"
inkscape:window-x="1182"
inkscape:window-y="932"
inkscape:window-maximized="1"
inkscape:current-layer="g10" />
<g
inkscape:groupmode="layer"
inkscape:label="Image"
id="g10"
transform="translate(-36.295882,-155.56359)">
<path
d="m 275.47475,596.59256 c 0,0 -5.57045,16.25525 -2.26517,28.71709 3.30528,12.46185 16.14089,37.99437 -2.7299,43.01501 -18.87079,5.02063 -17.86029,-21.31429 -17.53605,-23.41181 0.32424,-2.09752 7.39331,-27.47071 6.01109,-52.99575"
id="path841"
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 276.91561,665.35059 c 0,0 12.61189,8.44579 14.50873,-10.68153"
id="path847"
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 253.22194,645.08168 c 0,0 -11.01816,56.67539 -28.36563,95.24526"
id="path849"
sodipodi:nodetypes="cc"
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 244.57937,683.12347 c 0,0 3.2737,26.40483 9.54025,57.82357"
id="path851"
sodipodi:nodetypes="cc"
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 316.04674,497.63234 c 0,0 62.71911,92.14608 54.69476,243.32198"
id="path853"
sodipodi:nodetypes="cc"
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 120.06808,741.6296 c 0,0 -6.62708,-56.81125 8.51463,-136.07764 0,0 10.38277,-52.37528 6.92135,-77.56092"
id="path855"
sodipodi:nodetypes="ccc"
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 135.50406,527.99104 c 0,0 -3.81154,-4.17766 15.59765,-31.45559 0,0 2.60877,-1.97035 4.27961,-19.96442 0,0 -4.26771,-18.38982 -0.90695,-29.02908"
id="path857"
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 154.47437,447.54195 c 0,0 -3.22246,-21.67664 -15.69254,-40.07537 0,0 -5.65525,34.58955 -24.34696,61.05037 0,0 -6.06915,7.2186 -0.60673,13.38372"
id="path859"
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 113.82814,481.90067 c 0,0 8.2906,7.41008 21.67592,46.09037"
id="path861"
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 128.81165,604.54868 c 0,0 -64.030285,-53.11103 -70.652618,-87.58275 0,0 -0.190498,-5.4772 -12.31525,-16.03813 0,0 -5.931684,-4.74148 -2.683719,-17.79945 0,0 1.222082,-4.08813 0.936722,-5.49299 -0.28536,-1.40486 -15.879565,-52.62724 16.336608,-114.89948 0,0 3.684461,-7.51013 3.859208,-11.52805 0.174747,-4.01793 -4.770333,-53.55053 14.207228,-71.63893 18.97756,-18.0884 13.354543,-12.18511 35.389091,-31.66871 0,0 7.81837,-6.40344 7.74477,-9.50585 -0.0736,-3.1024 23.35598,-21.87076 47.93206,-34.74942 0,0 11.27377,-7.2633 11.02038,-19.28434 -0.2534,-12.02104 -4.20148,-25.01746 -4.20148,-25.01746"
id="path863"
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<g
transform="rotate(-24.597622,275.06212,612.76383)"
inkscape:transform-center-y="3.7858371"
inkscape:transform-center-x="-37.612849"
id="g868">
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path839"
d="m 301.91735,580.43313 c 0,0 20.81118,37.91217 29.99809,71.56868 0,0 -1.27474,2.72695 1.28783,3.73133 2.56257,1.00438 1.04672,7.25865 1.04672,7.25865 0,0 -2.91284,37.39221 -29.37703,14.46554 0,0 -1.27844,-0.52885 -2.43403,-3.12866 -1.15559,-2.59982 -2.25028,-3.46823 -3.00127,-4.09416 -0.75098,-0.62594 -17.48928,-34.97245 -26.66746,-57.11914" />
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path883"
d="m 333.16826,655.78513 c 0,0 -27.35631,-11.44567 -31.39538,16.61767" />
</g>
<path
sodipodi:nodetypes="cc"
d="m 258.95472,591.9171 c 0,0 -1.19161,-6.96253 6.25959,-16.53364"
id="path885"
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 316.04674,497.63234 c 0,0 -1.06007,-1.80628 -1.06103,-4.64592 -9.5e-4,-2.83965 3.44823,-19.38153 -0.53273,-33.49052 0,0 -2.58853,-11.54571 0.30044,-23.06558 2.88897,-11.51988 6.4611,-21.08418 5.34295,-32.33966 0,0 -0.28941,-1.91395 1.29432,-7.18164 1.58374,-5.26769 0.9702,-17.77481 0.52632,-19.31286 0,0 -0.9758,-7.57132 4.77059,-10.11194 0,0 5.70975,-4.12189 8.85414,-10.47351 0,0 -5.23319,-8.96335 2.55042,-10.41987"
id="path887"
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 342.99285,294.02579 c 0,0 2.40863,34.29764 -4.90069,52.56505 0,0 0.11318,6.77567 -2.55042,10.41987"
id="path889"
sodipodi:nodetypes="ccc"
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 315.57928,464.92242 c 0,0 -1.26908,-10.90421 0.85002,-17.80542 2.1191,-6.90121 12.41382,-29.04882 18.64462,-68.0488 0,0 -0.41097,-3.1104 4.79552,-12.60636 5.20649,-9.49597 4.31944,-23.71327 4.31944,-23.71327 0,0 -0.62263,-5.09044 0.52859,-10.37701 1.15122,-5.28657 0.90097,-9.52259 7.22618,-30.47694 6.32521,-20.95435 -0.69525,-46.50796 -0.5162,-47.42953 0.17904,-0.92158 -3.16691,-11.40884 -13.83809,-21.72476 -10.67118,-10.31592 -19.99048,-11.46923 -19.99048,-11.46923 0,0 -5.97561,-0.50521 -13.58234,-7.00204 -7.60674,-6.49682 -31.87768,-16.8264 -31.87768,-16.8264"
id="path893"
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 280.09799,468.44554 c 0,0 2.90232,-4.65637 11.26679,0.31348"
id="path895"
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 284.69236,466.46836 c 0,0 -4.41383,5.42951 -0.006,7.00776"
id="path897"
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<ellipse
style="opacity:1;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path903"
cx="85.181969"
cy="424.32559"
rx="9.2404251"
ry="4.6258879"
transform="rotate(-19.456297)" />
<ellipse
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path905"
cx="221.74664"
cy="372.79306"
rx="2.4708648"
ry="2.4088912" />
<path
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:7.56045;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path907"
sodipodi:type="arc"
sodipodi:cx="-280.18185"
sodipodi:cy="406.56561"
sodipodi:rx="2.0267301"
sodipodi:ry="1.6016541"
sodipodi:start="6.2560712"
sodipodi:end="3.2413906"
sodipodi:arc-type="slice"
d="m -278.15587,406.52219 a 2.0267301,1.6016541 0 0 1 -1.0394,1.4425 2.0267301,1.6016541 0 0 1 -2.09913,-0.0603 2.0267301,1.6016541 0 0 1 -0.9041,-1.49834 l 2.01665,0.15957 z"
transform="matrix(0.21840939,-0.97585723,0.98143007,0.1918203,0,0)" />
<path
d="m 138.78183,407.46658 c 0,0 3.10416,-7.61169 2.10619,-17.95459 0,0 -0.64177,-4.93172 0.83747,-9.14456 1.47925,-4.21285 4.08993,-12.80237 5.85172,-22.38993"
id="path909"
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 147.57721,357.9775 c 0,0 -0.25569,-1.24908 2.28661,-3.58316 2.54229,-2.33407 7.55128,-11.40353 9.73711,-19.99208"
id="path913"
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 226.49166,410.49362 c 0,0 -1.75222,14.98063 37.81976,6.59317"
id="path915"
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 230.09209,441.27245 c 0,0 3.08682,18.35365 39.82279,11.0958"
id="path917"
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 160.46824,474.23252 c 0,0 5.54755,17.65952 45.97744,22.23086"
id="path919"
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 267.47967,291.38393 c 0,0 12.90638,54.84957 6.43688,75.26399"
id="path921"
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 175.64273,372.31082 c 0,0 3.74111,22.67897 56.34499,16.53804"
id="path925"
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 245.53449,253.09208 c 0,0 -68.25989,-12.22844 -87.19681,-7.69003"
id="path927"
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 300.12954,225.9896 c 0,0 -24.55374,10.43093 -36.08466,27.88195"
id="path929"
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 332.63151,261.29222 c 0,0 -27.14867,-14.42698 -24.6289,-27.59554"
id="path931"
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 326.6876,367.48422 c 0,0 -7.45101,6.4638 -32.86038,8.19647"
id="path935"
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 231.66646,518.93745 c 0,0 11.89409,5.38851 20.24104,36.03176"
id="path937"
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 319.26342,402.92347 c 0,0 -0.66468,6.77058 -19.66362,11.10696"
id="path941"
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 314.05126,437.06207 c 0,0 -3.75693,9.33427 -19.48835,13.97482"
id="path943"
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
style="opacity:1;fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 316.04674,497.63234 c 0,0 -11.16016,13.50078 -10.55031,39.39049"
id="path945" />
<path
sodipodi:nodetypes="cc"
id="path870"
d="m 291.42434,654.66906 c 0,0 2.35422,-13.25577 -12.8767,-34.10306"
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,222 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="Ace Rockwood - outline.svg"
viewBox="0 0 338.93176 589.84552"
height="589.84552"
width="338.93176"
id="svg2"
version="1.1">
<metadata
id="metadata8">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs6" />
<sodipodi:namedview
inkscape:current-layer="g10"
inkscape:window-maximized="1"
inkscape:window-y="932"
inkscape:window-x="1182"
inkscape:cy="288.68986"
inkscape:cx="-140.67064"
inkscape:zoom="0.81780096"
showgrid="false"
id="namedview4"
inkscape:window-height="1024"
inkscape:window-width="1920"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
bordercolor="#666666"
pagecolor="#ffffff" />
<g
transform="translate(-36.295882,-155.56359)"
id="g10"
inkscape:label="Image"
inkscape:groupmode="layer">
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path839"
d="m 301.91735,580.43313 c 0,0 20.81118,37.91217 29.99809,71.56868 0,0 -1.27474,2.72695 1.28783,3.73133 2.56257,1.00438 1.04672,7.25865 1.04672,7.25865 0,0 -2.91284,37.39221 -29.37703,14.46554 0,0 -1.27844,-0.52885 -2.43403,-3.12866 -1.15559,-2.59982 -2.25028,-3.46823 -3.00127,-4.09416 -0.75098,-0.62594 -17.48928,-34.97245 -26.66746,-57.11914" />
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path841"
d="m 275.47475,596.59256 c 0,0 -5.57045,16.25525 -2.26517,28.71709 3.30528,12.46185 16.14089,37.99437 -2.7299,43.01501 -18.87079,5.02063 -17.86029,-21.31429 -17.53605,-23.41181 0.32424,-2.09752 7.39331,-27.47071 6.01109,-52.99575" />
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path847"
d="m 276.91561,665.35059 c 0,0 12.61189,8.44579 14.50873,-10.68153" />
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
sodipodi:nodetypes="cc"
id="path849"
d="m 253.22194,645.08168 c 0,0 -11.01816,56.67539 -28.36563,95.24526" />
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
sodipodi:nodetypes="cc"
id="path851"
d="m 244.57937,683.12347 c 0,0 3.2737,26.40483 9.54025,57.82357" />
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
sodipodi:nodetypes="cc"
id="path853"
d="m 316.04674,497.63234 c 0,0 62.71911,92.14608 54.69476,243.32198" />
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
sodipodi:nodetypes="ccc"
id="path855"
d="m 120.06808,741.6296 c 0,0 -6.62708,-56.81125 8.51463,-136.07764 0,0 10.38277,-52.37528 6.92135,-77.56092" />
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path857"
d="m 135.50406,527.99104 c 0,0 -3.81154,-4.17766 15.59765,-31.45559 0,0 2.60877,-1.97035 4.27961,-19.96442 0,0 -4.26771,-18.38982 -0.90695,-29.02908" />
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path859"
d="m 154.47437,447.54195 c 0,0 -3.22246,-21.67664 -15.69254,-40.07537 0,0 -5.65525,34.58955 -24.34696,61.05037 0,0 -6.06915,7.2186 -0.60673,13.38372" />
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path861"
d="m 113.82814,481.90067 c 0,0 8.2906,7.41008 21.67592,46.09037" />
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path863"
d="m 128.81165,604.54868 c 0,0 -64.030285,-53.11103 -70.652618,-87.58275 0,0 -0.190498,-5.4772 -12.31525,-16.03813 0,0 -5.931684,-4.74148 -2.683719,-17.79945 0,0 1.222082,-4.08813 0.936722,-5.49299 -0.28536,-1.40486 -15.879565,-52.62724 16.336608,-114.89948 0,0 3.684461,-7.51013 3.859208,-11.52805 0.174747,-4.01793 -4.770333,-53.55053 14.207228,-71.63893 18.97756,-18.0884 13.354543,-12.18511 35.389091,-31.66871 0,0 7.81837,-6.40344 7.74477,-9.50585 -0.0736,-3.1024 23.35598,-21.87076 47.93206,-34.74942 0,0 11.27377,-7.2633 11.02038,-19.28434 -0.2534,-12.02104 -4.20148,-25.01746 -4.20148,-25.01746" />
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path883"
d="m 333.16826,655.78513 c 0,0 -27.35631,-11.44567 -31.39538,16.61767" />
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path885"
d="m 258.95472,591.9171 c 0,0 -1.02835,-14.22935 13.05704,-19.53412" />
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path887"
d="m 316.04674,497.63234 c 0,0 -1.06007,-1.80628 -1.06103,-4.64592 -9.5e-4,-2.83965 3.44823,-19.38153 -0.53273,-33.49052 0,0 -2.58853,-11.54571 0.30044,-23.06558 2.88897,-11.51988 6.4611,-21.08418 5.34295,-32.33966 0,0 -0.28941,-1.91395 1.29432,-7.18164 1.58374,-5.26769 0.9702,-17.77481 0.52632,-19.31286 0,0 -0.9758,-7.57132 4.77059,-10.11194 0,0 5.70975,-4.12189 8.85414,-10.47351 0,0 -5.23319,-8.96335 2.55042,-10.41987" />
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
sodipodi:nodetypes="ccc"
id="path889"
d="m 342.99285,294.02579 c 0,0 2.40863,34.29764 -4.90069,52.56505 0,0 0.11318,6.77567 -2.55042,10.41987" />
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path893"
d="m 315.57928,464.92242 c 0,0 -1.26908,-10.90421 0.85002,-17.80542 2.1191,-6.90121 12.41382,-29.04882 18.64462,-68.0488 0,0 -0.41097,-3.1104 4.79552,-12.60636 5.20649,-9.49597 4.31944,-23.71327 4.31944,-23.71327 0,0 -0.62263,-5.09044 0.52859,-10.37701 1.15122,-5.28657 0.90097,-9.52259 7.22618,-30.47694 6.32521,-20.95435 -0.69525,-46.50796 -0.5162,-47.42953 0.17904,-0.92158 -3.16691,-11.40884 -13.83809,-21.72476 -10.67118,-10.31592 -19.99048,-11.46923 -19.99048,-11.46923 0,0 -5.97561,-0.50521 -13.58234,-7.00204 -7.60674,-6.49682 -31.87768,-16.8264 -31.87768,-16.8264" />
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path895"
d="m 280.09799,468.44554 c 0,0 2.90232,-4.65637 11.26679,0.31348" />
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path897"
d="m 284.69236,466.46836 c 0,0 -4.41383,5.42951 -0.006,7.00776" />
<ellipse
transform="rotate(-19.456297)"
ry="4.6258879"
rx="9.2404251"
cy="424.32559"
cx="85.181969"
id="path903"
style="opacity:1;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<ellipse
ry="2.4088912"
rx="2.4708648"
cy="372.79306"
cx="221.74664"
id="path905"
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
transform="matrix(0.21840939,-0.97585723,0.98143007,0.1918203,0,0)"
d="m -278.15587,406.52219 a 2.0267301,1.6016541 0 0 1 -1.0394,1.4425 2.0267301,1.6016541 0 0 1 -2.09913,-0.0603 2.0267301,1.6016541 0 0 1 -0.9041,-1.49834 l 2.01665,0.15957 z"
sodipodi:arc-type="slice"
sodipodi:end="3.2413906"
sodipodi:start="6.2560712"
sodipodi:ry="1.6016541"
sodipodi:rx="2.0267301"
sodipodi:cy="406.56561"
sodipodi:cx="-280.18185"
sodipodi:type="arc"
id="path907"
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:7.56045;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path909"
d="m 138.78183,407.46658 c 0,0 3.10416,-7.61169 2.10619,-17.95459 0,0 -0.64177,-4.93172 0.83747,-9.14456 1.47925,-4.21285 4.08993,-12.80237 5.85172,-22.38993" />
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path913"
d="m 147.57721,357.9775 c 0,0 -0.25569,-1.24908 2.28661,-3.58316 2.54229,-2.33407 7.55128,-11.40353 9.73711,-19.99208" />
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path915"
d="m 226.49166,410.49362 c 0,0 -1.75222,14.98063 37.81976,6.59317" />
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path917"
d="m 230.09209,441.27245 c 0,0 3.08682,18.35365 39.82279,11.0958" />
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path919"
d="m 160.46824,474.23252 c 0,0 5.54755,17.65952 45.97744,22.23086" />
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path921"
d="m 267.47967,291.38393 c 0,0 12.90638,54.84957 6.43688,75.26399" />
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path925"
d="m 175.64273,372.31082 c 0,0 3.74111,22.67897 56.34499,16.53804" />
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path927"
d="m 245.53449,253.09208 c 0,0 -68.25989,-12.22844 -87.19681,-7.69003" />
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path929"
d="m 300.12954,225.9896 c 0,0 -24.55374,10.43093 -36.08466,27.88195" />
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path931"
d="m 332.63151,261.29222 c 0,0 -27.14867,-14.42698 -24.6289,-27.59554" />
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path935"
d="m 326.6876,367.48422 c 0,0 -7.45101,6.4638 -32.86038,8.19647" />
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path937"
d="m 231.66646,518.93745 c 0,0 11.89409,5.38851 20.24104,36.03176" />
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path941"
d="m 319.26342,402.92347 c 0,0 -0.66468,6.77058 -19.66362,11.10696" />
<path
style="fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path943"
d="m 314.05126,437.06207 c 0,0 -3.75693,9.33427 -19.48835,13.97482" />
<path
id="path945"
d="m 316.04674,497.63234 c 0,0 -11.16016,13.50078 -10.55031,39.39049"
style="opacity:1;fill:none;stroke:#000000;stroke-width:7.55906;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

Some files were not shown because too many files have changed in this diff Show More