From 09c0032b3927051d08b3606d18d9bab8dacfcb27 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Fri, 7 Apr 2023 22:35:51 -0600 Subject: [PATCH] New landing page design --- app/Http/Controllers/LandingController.php | 45 ++ app/Http/Resources/DirectoryProfile.php | 37 ++ app/Services/LandingService.php | 104 ++++ .../assets/components/landing/Directory.vue | 139 +++++ .../assets/components/landing/Explore.vue | 119 ++++ resources/assets/components/landing/Index.vue | 231 ++++++++ .../assets/components/landing/NotFound.vue | 14 + .../components/landing/partials/PostCard.vue | 93 +++ .../components/landing/partials/UserCard.vue | 56 ++ .../components/landing/sections/footer.vue | 36 ++ .../components/landing/sections/nav.vue | 33 ++ resources/assets/js/landing.js | 295 ++++++++++ resources/assets/sass/landing.scss | 554 +++++++++++++++++- resources/views/site/index.blade.php | 143 +---- routes/api.php | 4 + routes/web.php | 2 + 16 files changed, 1779 insertions(+), 126 deletions(-) create mode 100644 app/Http/Controllers/LandingController.php create mode 100644 app/Http/Resources/DirectoryProfile.php create mode 100644 app/Services/LandingService.php create mode 100644 resources/assets/components/landing/Directory.vue create mode 100644 resources/assets/components/landing/Explore.vue create mode 100644 resources/assets/components/landing/Index.vue create mode 100644 resources/assets/components/landing/NotFound.vue create mode 100644 resources/assets/components/landing/partials/PostCard.vue create mode 100644 resources/assets/components/landing/partials/UserCard.vue create mode 100644 resources/assets/components/landing/sections/footer.vue create mode 100644 resources/assets/components/landing/sections/nav.vue create mode 100644 resources/assets/js/landing.js diff --git a/app/Http/Controllers/LandingController.php b/app/Http/Controllers/LandingController.php new file mode 100644 index 000000000..9d5d50d53 --- /dev/null +++ b/app/Http/Controllers/LandingController.php @@ -0,0 +1,45 @@ +user()) { + return redirect('/'); + } + + abort_if(config_cache('landing.show_directory') != 1, 404); + + return view('site.index'); + } + + public function exploreRedirect(Request $request) + { + if($request->user()) { + return redirect('/'); + } + + abort_if(config_cache('landing.show_explore_feed') != 1, 404); + + return view('site.index'); + } + + public function getDirectoryApi(Request $request) + { + abort_if(config_cache('landing.show_directory') != 1, 404); + + return DirectoryProfile::collection( + Profile::whereNull('domain') + ->whereIsSuggestable(true) + ->orderByDesc('updated_at') + ->cursorPaginate(20) + ); + } +} diff --git a/app/Http/Resources/DirectoryProfile.php b/app/Http/Resources/DirectoryProfile.php new file mode 100644 index 000000000..d5d4b5841 --- /dev/null +++ b/app/Http/Resources/DirectoryProfile.php @@ -0,0 +1,37 @@ +id, true); + if(!$account) { + return []; + } + + $url = url($this->username); + return [ + 'id' => $this->id, + 'name' => $this->name, + 'username' => $this->username, + 'url' => $url, + 'avatar' => $account['avatar'], + 'following_count' => $account['following_count'], + 'followers_count' => $account['followers_count'], + 'statuses_count' => $account['statuses_count'], + 'bio' => $account['note_text'] + ]; + } +} diff --git a/app/Services/LandingService.php b/app/Services/LandingService.php new file mode 100644 index 000000000..a59e91e80 --- /dev/null +++ b/app/Services/LandingService.php @@ -0,0 +1,104 @@ +where('last_active_at', '>', now()->subMonths(1)) + ->orWhere('created_at', '>', now()->subMonths(1)) + ->count(); + }); + + $totalUsers = Cache::remember('api:nodeinfo:users', 43200, function() { + return User::count(); + }); + + $postCount = Cache::remember('api:nodeinfo:statuses', 21600, function() { + return Status::whereLocal(true)->count(); + }); + + $contactAccount = Cache::remember('api:v1:instance-data:contact', 604800, function () { + $admin = User::whereIsAdmin(true)->first(); + return $admin && isset($admin->profile_id) ? + AccountService::getMastodon($admin->profile_id, true) : + null; + }); + + $rules = Cache::remember('api:v1:instance-data:rules', 604800, function () { + return config_cache('app.rules') ? + collect(json_decode(config_cache('app.rules'), true)) + ->map(function($rule, $key) { + $id = $key + 1; + return [ + 'id' => "{$id}", + 'text' => $rule + ]; + }) + ->toArray() : []; + }); + + $res = [ + 'name' => config_cache('app.name'), + 'url' => config_cache('app.url'), + 'domain' => config('pixelfed.domain.app'), + 'show_directory' => config_cache('landing.show_directory') == 1, + 'show_explore_feed' => config_cache('landing.show_explore_feed') == 1, + 'open_registration' => config_cache('pixelfed.open_registration') == 1, + 'version' => config('pixelfed.version'), + 'about' => [ + 'banner_image' => config_cache('app.banner_image') ?? url('/storage/headers/default.jpg'), + 'short_description' => config_cache('app.short_description'), + 'description' => config_cache('app.description'), + ], + 'stats' => [ + 'active_users' => (int) $activeMonth, + 'posts_count' => (int) $postCount, + 'total_users' => (int) $totalUsers + ], + 'contact' => [ + 'account' => $contactAccount, + 'email' => config('instance.email') + ], + 'rules' => $rules, + 'uploader' => [ + 'max_photo_size' => (int) (config('pixelfed.max_photo_size') * 1024), + 'max_caption_length' => (int) config('pixelfed.max_caption_length'), + 'max_altext_length' => (int) config('pixelfed.max_altext_length', 150), + 'album_limit' => (int) config_cache('pixelfed.max_album_length'), + 'image_quality' => (int) config_cache('pixelfed.image_quality'), + 'max_collection_length' => (int) config('pixelfed.max_collection_length', 18), + 'optimize_image' => (bool) config('pixelfed.optimize_image'), + 'optimize_video' => (bool) config('pixelfed.optimize_video'), + 'media_types' => config_cache('pixelfed.media_types'), + ], + 'features' => [ + 'federation' => config_cache('federation.activitypub.enabled'), + 'timelines' => [ + 'local' => true, + 'network' => (bool) config('federation.network_timeline'), + ], + 'mobile_apis' => (bool) config_cache('pixelfed.oauth_enabled'), + 'stories' => (bool) config_cache('instance.stories.enabled'), + 'video' => Str::contains(config_cache('pixelfed.media_types'), 'video/mp4'), + ] + ]; + + if($json) { + return json_encode($res); + } + + return $res; + } +} diff --git a/resources/assets/components/landing/Directory.vue b/resources/assets/components/landing/Directory.vue new file mode 100644 index 000000000..c8a1397b3 --- /dev/null +++ b/resources/assets/components/landing/Directory.vue @@ -0,0 +1,139 @@ + + + diff --git a/resources/assets/components/landing/Explore.vue b/resources/assets/components/landing/Explore.vue new file mode 100644 index 000000000..16152e7fc --- /dev/null +++ b/resources/assets/components/landing/Explore.vue @@ -0,0 +1,119 @@ + + + diff --git a/resources/assets/components/landing/Index.vue b/resources/assets/components/landing/Index.vue new file mode 100644 index 000000000..0059a64e8 --- /dev/null +++ b/resources/assets/components/landing/Index.vue @@ -0,0 +1,231 @@ + + + diff --git a/resources/assets/components/landing/NotFound.vue b/resources/assets/components/landing/NotFound.vue new file mode 100644 index 000000000..036aed662 --- /dev/null +++ b/resources/assets/components/landing/NotFound.vue @@ -0,0 +1,14 @@ + diff --git a/resources/assets/components/landing/partials/PostCard.vue b/resources/assets/components/landing/partials/PostCard.vue new file mode 100644 index 000000000..2999e1536 --- /dev/null +++ b/resources/assets/components/landing/partials/PostCard.vue @@ -0,0 +1,93 @@ + + + diff --git a/resources/assets/components/landing/partials/UserCard.vue b/resources/assets/components/landing/partials/UserCard.vue new file mode 100644 index 000000000..eabf20b69 --- /dev/null +++ b/resources/assets/components/landing/partials/UserCard.vue @@ -0,0 +1,56 @@ + + + diff --git a/resources/assets/components/landing/sections/footer.vue b/resources/assets/components/landing/sections/footer.vue new file mode 100644 index 000000000..6b9f3ed83 --- /dev/null +++ b/resources/assets/components/landing/sections/footer.vue @@ -0,0 +1,36 @@ + + + diff --git a/resources/assets/components/landing/sections/nav.vue b/resources/assets/components/landing/sections/nav.vue new file mode 100644 index 000000000..38fde6ef2 --- /dev/null +++ b/resources/assets/components/landing/sections/nav.vue @@ -0,0 +1,33 @@ + + + diff --git a/resources/assets/js/landing.js b/resources/assets/js/landing.js new file mode 100644 index 000000000..89be5e968 --- /dev/null +++ b/resources/assets/js/landing.js @@ -0,0 +1,295 @@ +require('./polyfill'); +import Vue from 'vue'; +window.Vue = Vue; +import VueRouter from "vue-router"; +import Vuex from "vuex"; +import { sync } from "vuex-router-sync"; +import BootstrapVue from 'bootstrap-vue' +import InfiniteLoading from 'vue-infinite-loading'; +import Loading from 'vue-loading-overlay'; +import VueTimeago from 'vue-timeago'; +import VueCarousel from 'vue-carousel'; +import VueBlurHash from 'vue-blurhash'; +import VueMasonry from 'vue-masonry-css'; +import VueI18n from 'vue-i18n'; +window.pftxt = require('twitter-text'); +import 'vue-blurhash/dist/vue-blurhash.css' +window.filesize = require('filesize'); +import swal from 'sweetalert'; +window._ = require('lodash'); +window.Popper = require('popper.js').default; +window.pixelfed = window.pixelfed || {}; +window.$ = window.jQuery = require('jquery'); +require('bootstrap'); +window.axios = require('axios'); +window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; +require('readmore-js'); +window.blurhash = require("blurhash"); + +$('[data-toggle="tooltip"]').tooltip() +let token = document.head.querySelector('meta[name="csrf-token"]'); +if (token) { + window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content; +} else { + console.error('CSRF token not found.'); +} + +Vue.use(VueRouter); +Vue.use(Vuex); +Vue.use(VueBlurHash); +Vue.use(VueCarousel); +Vue.use(BootstrapVue); +Vue.use(InfiniteLoading); +Vue.use(Loading); +Vue.use(VueMasonry); +Vue.use(VueI18n); +Vue.use(VueTimeago, { + name: 'Timeago', + locale: 'en' +}); + +Vue.component( + 'photo-presenter', + require('./components/presenter/PhotoPresenter.vue').default +); + +Vue.component( + 'video-presenter', + require('./components/presenter/VideoPresenter.vue').default +); + +Vue.component( + 'photo-album-presenter', + require('./components/presenter/PhotoAlbumPresenter.vue').default +); + +Vue.component( + 'video-album-presenter', + require('./components/presenter/VideoAlbumPresenter.vue').default +); + +Vue.component( + 'mixed-album-presenter', + require('./components/presenter/MixedAlbumPresenter.vue').default +); + +Vue.component( + 'navbar', + require('./../components/landing/sections/nav.vue').default +); + +Vue.component( + 'footer-component', + require('./../components/landing/sections/footer.vue').default +); + +import IndexComponent from "./../components/landing/Index.vue"; +import DirectoryComponent from "./../components/landing/Directory.vue"; +import ExploreComponent from "./../components/landing/Explore.vue"; +import NotFoundComponent from "./../components/landing/NotFound.vue"; + +const router = new VueRouter({ + mode: "history", + linkActiveClass: "", + linkExactActiveClass: "active", + + routes: [ + { + path: "/", + component: IndexComponent + }, + { + path: "/web/directory", + component: DirectoryComponent + }, + { + path: "/web/explore", + component: ExploreComponent + }, + { + path: "/*", + component: NotFoundComponent, + props: true + }, + ], + + scrollBehavior(to, from, savedPosition) { + if (to.hash) { + return { + selector: `[id='${to.hash.slice(1)}']` + }; + } else { + return { x: 0, y: 0 }; + } + } +}); + +function lss(name, def) { + let key = 'pf_m2s.' + name; + let ls = window.localStorage; + if(ls.getItem(key)) { + let val = ls.getItem(key); + if(['pl', 'color-scheme'].includes(name)) { + return val; + } + return ['true', true].includes(val); + } + return def; +} + +const store = new Vuex.Store({ + state: { + version: 1, + hideCounts: true, + autoloadComments: false, + newReactions: false, + fixedHeight: false, + profileLayout: 'grid', + showDMPrivacyWarning: true, + relationships: {}, + emoji: [], + colorScheme: lss('color-scheme', 'system'), + }, + + getters: { + getVersion: state => { + return state.version; + }, + + getHideCounts: state => { + return state.hideCounts; + }, + + getAutoloadComments: state => { + return state.autoloadComments; + }, + + getNewReactions: state => { + return state.newReactions; + }, + + getFixedHeight: state => { + return state.fixedHeight; + }, + + getProfileLayout: state => { + return state.profileLayout; + }, + + getRelationship: (state) => (id) => { + return state.relationships[id]; + }, + + getCustomEmoji: state => { + return state.emoji; + }, + + getColorScheme: state => { + return state.colorScheme; + }, + + getShowDMPrivacyWarning: state => { + return state.showDMPrivacyWarning; + } + }, + + mutations: { + setVersion(state, value) { + state.version = value; + }, + + setHideCounts(state, value) { + localStorage.setItem('pf_m2s.hc', value); + state.hideCounts = value; + }, + + setAutoloadComments(state, value) { + localStorage.setItem('pf_m2s.ac', value); + state.autoloadComments = value; + }, + + setNewReactions(state, value) { + localStorage.setItem('pf_m2s.nr', value); + state.newReactions = value; + }, + + setFixedHeight(state, value) { + localStorage.setItem('pf_m2s.fh', value); + state.fixedHeight = value; + }, + + setProfileLayout(state, value) { + localStorage.setItem('pf_m2s.pl', value); + state.profileLayout = value; + }, + + updateRelationship(state, relationships) { + relationships.forEach((relationship) => { + Vue.set(state.relationships, relationship.id, relationship) + }) + }, + + updateCustomEmoji(state, emojis) { + state.emoji = emojis; + }, + + setColorScheme(state, value) { + if(state.colorScheme == value) { + return; + } + localStorage.setItem('pf_m2s.color-scheme', value); + state.colorScheme = value; + const name = value == 'system' ? '' : (value == 'light' ? 'force-light-mode' : 'force-dark-mode'); + document.querySelector("body").className = name; + if(name != 'system') { + const payload = name == 'force-dark-mode' ? { dark_mode: 'on' } : {}; + axios.post('/settings/labs', payload); + } + }, + + setShowDMPrivacyWarning(state, value) { + localStorage.setItem('pf_m2s.dmpwarn', value); + state.showDMPrivacyWarning = value; + } + }, +}); + +let i18nMessages = { + en: require('./i18n/en.json'), + ar: require('./i18n/ar.json'), + ca: require('./i18n/ca.json'), + de: require('./i18n/de.json'), + el: require('./i18n/el.json'), + es: require('./i18n/es.json'), + eu: require('./i18n/eu.json'), + fr: require('./i18n/fr.json'), + he: require('./i18n/he.json'), + gd: require('./i18n/gd.json'), + gl: require('./i18n/gl.json'), + id: require('./i18n/id.json'), + it: require('./i18n/it.json'), + ja: require('./i18n/ja.json'), + nl: require('./i18n/nl.json'), + pl: require('./i18n/pl.json'), + pt: require('./i18n/pt.json'), + ru: require('./i18n/ru.json'), + uk: require('./i18n/uk.json'), + vi: require('./i18n/vi.json'), +}; + +let locale = document.querySelector('html').getAttribute('lang'); + +const i18n = new VueI18n({ + locale: locale, // set locale + fallbackLocale: 'en', + messages: i18nMessages +}); + +sync(store, router); + +const App = new Vue({ + el: '#content', + i18n, + router, + store +}); diff --git a/resources/assets/sass/landing.scss b/resources/assets/sass/landing.scss index e07472856..3865982ec 100644 --- a/resources/assets/sass/landing.scss +++ b/resources/assets/sass/landing.scss @@ -2,12 +2,556 @@ @import "fonts"; @import "lib/fontawesome"; +@import "lib/inter"; +@import "lib/manrope"; @import 'variables'; @import '~bootstrap/scss/bootstrap'; @import 'custom'; -.container.slim { - width: auto; - max-width: 680px; - padding: 0 15px; -} \ No newline at end of file +body { + background: #080e2b; + font-family: 'Manrope', sans-serif; + color: #fff; +} + +.bg-black { + background-color: #080e2b; + transition: background-color 0.3s ease; +} + +.navbar { + padding-top: 20px; + padding-bottom: 20px; + + &-brand { + display: flex; + align-items: center; + gap: 10px; + + span { + font-weight: bold; + font-size: 24px; + } + } + + .nav-link { + &.active { + font-weight: bold; + } + } + + @include media-breakpoint-up(lg) { + .nav-link { + margin-right: 1rem !important; + } + } +} + +.bg-bluegray { + &-700 { + background-color: #334155; + } + + &-800 { + background-color: #1e293b; + } + + &-900 { + background-color: #0f172a; + } +} + +.text-bluegray { + &-400 { + color: #94a3b8; + } + &-500 { + color: #64748b; + } + &-600 { + color: #475569; + } +} + +.page-wrapper { + position: relative; + padding-top: 3rem; + padding-bottom: 3rem; + min-height: 100vh !important; + background-color: #212529 !important; + background-image: url("/_landing/bg.jpg"); + background-size: cover !important; + background-repeat: no-repeat !important; + background-position: center !important; +} + +.container-compact { + max-width: 600px; + margin-top: 3rem; + padding-top: 3rem; + padding-left: 0.25rem; + padding-right: 0.25rem; + + @media (min-width: 768px) { + padding-top: 0 !important; + } +} + +.overflow-hidden { + overflow: hidden !important; +} + +.bg-glass { + background: rgba(255, 255, 255, 0.05); + border-radius: 16px; + box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1); + backdrop-filter: blur(5px); + -webkit-backdrop-filter: blur(5px); + border: 1px solid rgba(255, 255, 255, 0.05); + margin-bottom: -1px; +} + +.text-gradient-primary { + background: linear-gradient(to right, #6366f1, #8B5CF6, #D946EF); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} + +.gap-3 { + gap: 3rem; +} + +.btn-primary-alt { + border: none; + outline: none; + color: white; + position: relative; + z-index: 1; + cursor: pointer; + background: none; + text-shadow: 3px 3px 10px rgba(0,0,0,.45); + &:before, &:after { + position: absolute; + top: 50%; + left: 50%; + border-radius: 10em; + transform: translateX(-50%) translateY(-50%); + width: 105%; + height: 105%; + content: ''; + z-index: -2; + background-size: 400% 400%; + background: linear-gradient(60deg, #f79533, #f37055, #ef4e7b, #a166ab, #5073b8, #1098ad, #07b39b, #6fba82); + } + &:before { + filter: blur(7px); + transition: all .25s ease; + animation: pulse 10s infinite ease; + } + &:after { + filter: blur(0.3px); + } + &:hover { + &:before { + width: 115%; + height: 115%; + } + } +} + +.opacity-50 { + opacity: 50%; +} + +.opacity-30 { + opacity: 30%; +} + +.nav-menu { + border-bottom: 1px solid #334155; + .nav-link { + color: #94a3b8; + position: relative; + font-size: 12px; + + @media(min-width: 768px) { + font-size: 16px; + } + + &.active { + color: #ffffff; + font-weight: 600; + } + + &.active:before, + &.active:after, + &.nav-item:hover:before, + &.nav-item:hover:after { + content: ' '; + position: absolute; + border: solid 10px transparent; + border-bottom: solid 0px transparent; + border-width: 10px; + bottom: -12px; + left: 50%; + margin-left: -10px; + border-color: transparent transparent #334155; + } + + &.active:after, + &.nav-item:hover:after { + bottom: -14px; + border-color: transparent transparent #0f172a; + } + } +} + +.landing-index-component { + width: 100%; + overflow: hidden; + + .logo { + margin-right: 10px; + } + + h1 { + color: var(--light); + font-size: 4em; + font-weight: bold; + margin-bottom: 0; + } + + p { + color: var(--light); + } + + .server-header { + margin: 0 0 30px 0; + + &-domain { + text-align: center; + font-size: 25px; + font-weight: 700; + } + + &-attribution { + font-size: 16px; + text-align: center; + color: #94a3b8; + letter-spacing: 0.6px; + + a { + color: #ffffff; + font-weight: 800; + } + } + } + + .server-stats { + margin: 30px 0; + + .list-group { + flex-direction: column; + border-color: #1e293b; + + @media (min-width: 768px) { + flex-direction: row; + + &-item { + border-color: #1e293b; + flex-grow: 1; + border-top-width: 1px; + border-left-width: 0; + + &:first-child { + border-left-width: 1px; + } + + &:last-child { + border-top-right-radius: 0.25rem; + border-bottom-left-radius: 0; + } + } + } + + &-item { + border-color: #1e293b; + } + } + + .stat-value { + font-size: 20px; + font-weight: 700; + color: #ffffff; + margin-bottom: 0; + } + + .stat-label { + font-size: 12px; + font-weight: 700; + color: #64748b; + margin-bottom: 0; + text-transform: uppercase; + letter-spacing: 0.8px; + } + } + + .server-admin { + margin: 30px 0; + + .list-group { + flex-direction: column; + border-color: #1e293b; + + @media (min-width: 768px) { + flex-direction: row; + + &-item { + border-color: #1e293b; + flex-grow: 1; + border-top-width: 1px; + border-left-width: 0; + + &:first-child { + border-left-width: 1px; + } + + &:last-child { + border-top-right-radius: 0.25rem; + border-bottom-left-radius: 0; + } + } + } + + &-item { + border-color: #1e293b; + } + } + + .item-label { + color: #475569; + text-transform: uppercase; + font-weight: 500; + letter-spacing: 1px; + } + + .admin-card { + text-decoration: none; + + .d-flex { + gap: 10px; + } + + .avatar { + border-radius: 6px; + } + + .user-info { + .display-name { + color: #94a3b8; + } + + .username { + font-weight: 700; + } + + .display-name, + .username { + margin-bottom: 0; + } + } + } + + .admin-email { + color: #ffffff; + font-size: 15px; + font-weight: 700; + text-decoration: none; + } + } + + .accordion { + .btn-block { + display: flex; + justify-content: space-between; + align-items: center; + text-decoration: none; + + .h5 { + margin-bottom: 0; + } + + &:focus { + box-shadow: none; + } + + .far { + color: #cbd5e1; + } + + .text-white { + .far { + color: #94a3b8; + } + } + } + + .about-text { + padding: 40px 24px; + + p { + font-size: 17px; + } + + p:last-child { + margin-bottom: 0; + } + } + + .list-group-rules { + .list-group-item { + display: flex; + gap: 10px; + align-items: center; + border-color: #475569; + + .rule-id { + color: #475569; + font-size: 20px; + } + + .rule-text { + color: #fff; + } + } + } + + .card-features { + &-cloud { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 10px; + padding: 10px 5px; + margin-bottom: 20px; + + .badge { + font-size: 13px; + font-weight: 400; + padding: 5px 10px; + + &-success { + background: #86efac30; + } + + .far { + margin-right: 5px; + color: #22c55e; + } + } + } + + .list-group-features { + .list-group-item { + display: flex; + justify-content: space-between; + align-items: center; + border-color: #475569; + + .feature-label { + font-size: 15px; + } + + .fa-times-circle { + color: #f43f5e; + } + + .fa-check-circle { + color: #22c55e; + } + } + } + } + } +} + +.landing-directory-component { + .feed-list { + display: flex; + flex-direction: column; + gap: 20px; + } + + .landing-user-card { + .display-name { + a { + @extend .text-bluegray-400; + font-size: 12px; + font-weight: 500; + text-decoration: none; + } + } + + .username { + margin-bottom: 2px; + + a { + color: #fff; + font-size: 18px; + font-weight: 800; + text-decoration: none; + } + } + + .user-stats { + display: flex; + justify-content: space-between; + + &-item { + @extend .text-bluegray-500; + font-size: 13px; + font-weight: 600; + } + } + + .user-bio { + @extend .bg-bluegray-700; + margin-top: 1rem; + padding: 15px; + border-radius: 10px; + } + } +} + +.landing-explore-component { + .feed-list { + display: flex; + flex-direction: column; + gap: 20px; + + .landing-post-card { + a.text-bluegray-400 { + &:hover { + color: #cbd5e1; + text-decoration: none; + } + } + + a.text-bluegray-500 { + &:hover { + color: #94a3b8; + text-decoration: none; + } + } + + .read-more-component { + color: #64748b; + a { + color: #94a3b8; + font-weight: 600; + } + } + } + + } +} diff --git a/resources/views/site/index.blade.php b/resources/views/site/index.blade.php index c2f59984a..d3fcfa013 100644 --- a/resources/views/site/index.blade.php +++ b/resources/views/site/index.blade.php @@ -1,7 +1,6 @@ - @@ -9,7 +8,7 @@ - {{ config('app.name', 'Laravel') }} + {{ config('app.name', 'Pixelfed') }} @@ -24,125 +23,27 @@ - + - -
-
-
-
-
-
-

Photo Sharing

-

For Everyone.

-
- -

{{ config_cache('app.short_description') ?? 'Pixelfed is an image sharing platform, an ethical alternative to centralized platforms.' }}

-

Learn more

+ +
+
-
-
- @include('layouts.partial.footer') - + + + + + + + + diff --git a/routes/api.php b/routes/api.php index e462cda5c..f05489c7f 100644 --- a/routes/api.php +++ b/routes/api.php @@ -214,4 +214,8 @@ Route::group(['prefix' => 'api'], function() use($middleware) { Route::post('instances/moderate', 'Api\AdminApiController@moderateInstance')->middleware($middleware); Route::post('instances/refresh-stats', 'Api\AdminApiController@refreshInstanceStats')->middleware($middleware); }); + + Route::group(['prefix' => 'landing/v1'], function() use($middleware) { + Route::get('directory', 'LandingController@getDirectoryApi'); + }); }); diff --git a/routes/web.php b/routes/web.php index 5e4919f40..828b13fcf 100644 --- a/routes/web.php +++ b/routes/web.php @@ -150,6 +150,8 @@ Route::domain(config('portfolio.domain'))->group(function () { Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofactor', 'localization'])->group(function () { Route::get('/', 'SiteController@home')->name('timeline.personal'); Route::redirect('/home', '/')->name('home'); + Route::get('web/directory', 'LandingController@directoryRedirect'); + Route::get('web/explore', 'LandingController@exploreRedirect'); Auth::routes();