You've already forked AstralRinth
forked from didirus/AstralRinth
Improve ad security, add CMP changes (#2399)
This commit is contained in:
@@ -4,7 +4,7 @@ import { get as getCreds } from '@/helpers/mr_auth.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { get_user } from '@/helpers/cache.js'
|
||||
import { ChevronRightIcon } from '@modrinth/assets'
|
||||
import { init_ads_window } from '@/helpers/ads.js'
|
||||
import { init_ads_window, open_ads_link, record_ads_click } from '@/helpers/ads.js'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
|
||||
const showAd = ref(true)
|
||||
@@ -73,6 +73,11 @@ function updateAdPosition(overrideShown = false) {
|
||||
}
|
||||
}
|
||||
|
||||
async function openPlusLink() {
|
||||
await record_ads_click()
|
||||
await open_ads_link('https://modrinth.com/plus', 'https://modrinth.com')
|
||||
}
|
||||
|
||||
const unlisten = await listen('ads-scroll', (event) => {
|
||||
if (adsWrapper.value) {
|
||||
adsWrapper.value.parentNode.scrollTop += event.payload.scroll
|
||||
@@ -105,17 +110,17 @@ onUnmounted(() => {
|
||||
class="ad-parent relative mb-3 flex w-full justify-center rounded-2xl bg-bg-raised cursor-pointer"
|
||||
>
|
||||
<div class="flex max-h-[250px] min-h-[250px] min-w-[300px] max-w-[300px] flex-col gap-4 p-6">
|
||||
<p class="m-0 text-2xl font-bold text-contrast">90% of ad revenue goes to creators</p>
|
||||
<a
|
||||
href="https://modrinth.com/plus"
|
||||
class="mt-auto items-center gap-1 text-purple hover:underline"
|
||||
<p class="m-0 text-2xl font-bold text-contrast">75% of ad revenue goes to creators</p>
|
||||
<button
|
||||
class="mt-auto items-center gap-1 text-purple hover:underline bg-transparent border-none text-left cursor-pointer outline-none"
|
||||
@click="openPlusLink"
|
||||
>
|
||||
<span>
|
||||
Support creators and Modrinth ad-free with
|
||||
<span class="font-bold">Modrinth+</span>
|
||||
</span>
|
||||
<ChevronRightIcon class="relative top-[3px] h-5 w-5" />
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -11,3 +11,11 @@ export async function show_ads_window() {
|
||||
export async function hide_ads_window(reset) {
|
||||
return await invoke('plugin:ads|hide_ads_window', { reset })
|
||||
}
|
||||
|
||||
export async function record_ads_click() {
|
||||
return await invoke('plugin:ads|record_ads_click')
|
||||
}
|
||||
|
||||
export async function open_ads_link(path, origin) {
|
||||
return await invoke('plugin:ads|open_link', { path, origin })
|
||||
}
|
||||
|
||||
@@ -226,6 +226,8 @@ fn main() {
|
||||
"hide_ads_window",
|
||||
"scroll_ads_window",
|
||||
"show_ads_window",
|
||||
"record_ads_click",
|
||||
"open_link",
|
||||
])
|
||||
.default_permission(
|
||||
DefaultPermissionRule::AllowAllCommands,
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
"ads-window"
|
||||
],
|
||||
"permissions": [
|
||||
"shell:allow-open",
|
||||
"ads:default"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
{"ads":{"identifier":"ads","description":"","remote":{"urls":["https://modrinth.com/*","http://localhost:3000/*"]},"local":false,"webviews":["ads-window"],"permissions":["shell:allow-open","ads:default"]},"core":{"identifier":"core","description":"","local":true,"windows":["main"],"permissions":["core:default","core:path:default","core:event:default","core:window:default","core:app:default","core:resources:default","core:menu:default","core:tray:default","core:window:allow-create","core:window:allow-maximize","core:window:allow-toggle-maximize","core:window:allow-unmaximize","core:window:allow-minimize","core:window:allow-unminimize","core:window:allow-show","core:window:allow-hide","core:window:allow-close","core:window:allow-set-decorations","core:window:allow-start-dragging","core:webview:allow-set-webview-zoom"]},"plugins":{"identifier":"plugins","description":"","local":true,"windows":["main"],"permissions":["dialog:allow-open","dialog:allow-confirm","shell:allow-open","os:allow-platform","os:allow-version","os:allow-os-type","os:allow-family","os:allow-arch","os:allow-exe-extension","os:allow-locale","os:allow-hostname","deep-link:default","window-state:default","window-state:allow-restore-state","window-state:allow-save-window-state","auth:default","import:default","jre:default","logs:default","metadata:default","mr-auth:default","profile-create:default","pack:default","process:default","profile:default","cache:default","settings:default","tags:default","utils:default","ads:default"]},"updater":{"identifier":"updater","description":"","local":true,"windows":["main"],"permissions":["updater:default"]}}
|
||||
{"ads":{"identifier":"ads","description":"","remote":{"urls":["https://modrinth.com/*","http://localhost:3000/*"]},"local":false,"webviews":["ads-window"],"permissions":["ads:default"]},"core":{"identifier":"core","description":"","local":true,"windows":["main"],"permissions":["core:default","core:path:default","core:event:default","core:window:default","core:app:default","core:resources:default","core:menu:default","core:tray:default","core:window:allow-create","core:window:allow-maximize","core:window:allow-toggle-maximize","core:window:allow-unmaximize","core:window:allow-minimize","core:window:allow-unminimize","core:window:allow-show","core:window:allow-hide","core:window:allow-close","core:window:allow-set-decorations","core:window:allow-start-dragging","core:webview:allow-set-webview-zoom"]},"plugins":{"identifier":"plugins","description":"","local":true,"windows":["main"],"permissions":["dialog:allow-open","dialog:allow-confirm","shell:allow-open","os:allow-platform","os:allow-version","os:allow-os-type","os:allow-family","os:allow-arch","os:allow-exe-extension","os:allow-locale","os:allow-hostname","deep-link:default","window-state:default","window-state:allow-restore-state","window-state:allow-save-window-state","auth:default","import:default","jre:default","logs:default","metadata:default","mr-auth:default","profile-create:default","pack:default","process:default","profile:default","cache:default","settings:default","tags:default","utils:default","ads:default"]},"updater":{"identifier":"updater","description":"","local":true,"windows":["main"],"permissions":["updater:default"]}}
|
||||
@@ -320,6 +320,20 @@
|
||||
"ads:allow-init-ads-window"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "ads:allow-open-link -> Enables the open_link command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ads:allow-open-link"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "ads:allow-record-ads-click -> Enables the record_ads_click command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ads:allow-record-ads-click"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "ads:allow-scroll-ads-window -> Enables the scroll_ads_window command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -348,6 +362,20 @@
|
||||
"ads:deny-init-ads-window"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "ads:deny-open-link -> Denies the open_link command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ads:deny-open-link"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "ads:deny-record-ads-click -> Denies the record_ads_click command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ads:deny-record-ads-click"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "ads:deny-scroll-ads-window -> Denies the scroll_ads_window command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
|
||||
@@ -320,6 +320,20 @@
|
||||
"ads:allow-init-ads-window"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "ads:allow-open-link -> Enables the open_link command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ads:allow-open-link"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "ads:allow-record-ads-click -> Enables the record_ads_click command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ads:allow-record-ads-click"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "ads:allow-scroll-ads-window -> Enables the scroll_ads_window command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -348,6 +362,20 @@
|
||||
"ads:deny-init-ads-window"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "ads:deny-open-link -> Denies the open_link command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ads:deny-open-link"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "ads:deny-record-ads-click -> Denies the record_ads_click command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ads:deny-record-ads-click"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "ads:deny-scroll-ads-window -> Denies the scroll_ads_window command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
if (!window.modrinthClickListener) {
|
||||
window.modrinthClickListener = true
|
||||
document.addEventListener('click', function (e) {
|
||||
document.addEventListener(
|
||||
'click',
|
||||
function (e) {
|
||||
window.top.postMessage({ modrinthAdClick: true }, 'https://modrinth.com')
|
||||
|
||||
let target = e.target
|
||||
while (target != null) {
|
||||
if (target.matches('a')) {
|
||||
@@ -12,8 +14,9 @@ if (!window.modrinthClickListener) {
|
||||
}
|
||||
target = target.parentElement
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
true,
|
||||
)
|
||||
|
||||
window.open = (url, target, features) => {
|
||||
window.top.postMessage({ modrinthOpenUrl: url }, 'https://modrinth.com')
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
use serde::Serialize;
|
||||
use std::collections::HashSet;
|
||||
use std::time::{Duration, Instant};
|
||||
use tauri::plugin::TauriPlugin;
|
||||
use tauri::{Emitter, LogicalPosition, LogicalSize, Manager, Runtime};
|
||||
use tauri::{
|
||||
Emitter, Listener, LogicalPosition, LogicalSize, Manager, Runtime,
|
||||
};
|
||||
use tauri_plugin_shell::{open, ShellExt};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
pub struct AdsState {
|
||||
pub shown: bool,
|
||||
pub size: Option<LogicalSize<f32>>,
|
||||
pub position: Option<LogicalPosition<f32>>,
|
||||
pub last_click: Option<Instant>,
|
||||
pub malicious_origins: HashSet<String>,
|
||||
}
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
@@ -16,6 +23,8 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
shown: true,
|
||||
size: None,
|
||||
position: None,
|
||||
last_click: None,
|
||||
malicious_origins: HashSet::new(),
|
||||
}));
|
||||
|
||||
// We refresh the ads window every 5 minutes for performance
|
||||
@@ -43,6 +52,8 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
hide_ads_window,
|
||||
scroll_ads_window,
|
||||
show_ads_window,
|
||||
record_ads_click,
|
||||
open_link,
|
||||
])
|
||||
.build()
|
||||
}
|
||||
@@ -75,7 +86,7 @@ pub async fn init_ads_window<R: Runtime>(
|
||||
let _ = webview.set_size(LogicalSize::new(width, height));
|
||||
}
|
||||
} else if let Some(window) = app.get_window("main") {
|
||||
let _ = window.add_child(
|
||||
let window = window.add_child(
|
||||
tauri::webview::WebviewBuilder::new(
|
||||
"ads-window",
|
||||
WebviewUrl::External(
|
||||
@@ -93,6 +104,12 @@ pub async fn init_ads_window<R: Runtime>(
|
||||
},
|
||||
LogicalSize::new(width, height),
|
||||
);
|
||||
|
||||
if let Ok(window) = window {
|
||||
window.listen_any("click", |event| {
|
||||
println!("click: {:?}", event);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -159,3 +176,43 @@ pub async fn scroll_ads_window<R: Runtime>(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn record_ads_click<R: Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
) -> crate::api::Result<()> {
|
||||
let state = app.state::<RwLock<AdsState>>();
|
||||
|
||||
let mut state = state.write().await;
|
||||
state.last_click = Some(Instant::now());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn open_link<R: Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
path: String,
|
||||
origin: String,
|
||||
) -> crate::api::Result<()> {
|
||||
let state = app.state::<RwLock<AdsState>>();
|
||||
let mut state = state.write().await;
|
||||
|
||||
if url::Url::parse(&path).is_ok()
|
||||
&& !state.malicious_origins.contains(&origin)
|
||||
{
|
||||
if let Some(last_click) = state.last_click {
|
||||
if last_click.elapsed() < Duration::from_millis(100) {
|
||||
let _ = app.shell().open(&path, None);
|
||||
state.last_click = None;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tracing::info!("Malicious click: {path} origin {origin}");
|
||||
state.malicious_origins.insert(origin);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="ad-parent relative mb-3 flex w-full justify-center rounded-2xl bg-bg-raised">
|
||||
<div class="flex max-h-[250px] min-h-[250px] min-w-[300px] max-w-[300px] flex-col gap-4 p-6">
|
||||
<p class="m-0 text-2xl font-bold text-contrast">90% of ad revenue goes to creators</p>
|
||||
<p class="m-0 text-2xl font-bold text-contrast">75% of ad revenue goes to creators</p>
|
||||
<nuxt-link to="/plus" class="mt-auto items-center gap-1 text-purple hover:underline">
|
||||
<span>
|
||||
Support creators and Modrinth ad-free with
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
You have
|
||||
<strong>{{ $formatMoney(userBalance.available) }}</strong>
|
||||
available to withdraw. <strong>{{ $formatMoney(userBalance.pending) }}</strong> of your
|
||||
balance is <a class="text-link" href="#">pending</a>.
|
||||
balance is <nuxt-link class="text-link" to="/legal/cmp-info#pending">pending</nuxt-link>.
|
||||
</p>
|
||||
</div>
|
||||
<p v-else>
|
||||
@@ -15,7 +15,7 @@
|
||||
<strong>{{ $formatMoney(userBalance.available) }}</strong
|
||||
>, which is under the minimum of ${{ minWithdraw }} to withdraw.
|
||||
<strong>{{ $formatMoney(userBalance.pending) }}</strong> of your balance is
|
||||
<a class="text-link" href="#">pending</a>.
|
||||
<nuxt-link class="text-link" to="/legal/cmp-info#pending">pending</nuxt-link>.
|
||||
</p>
|
||||
<div class="input-group mt-4">
|
||||
<nuxt-link
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="markdown-body">
|
||||
<h1>Rewards Program Information</h1>
|
||||
<p><em>Last modified: May 13, 2024</em></p>
|
||||
<p><em>Last modified: Sep 12, 2024</em></p>
|
||||
<p>
|
||||
This page was created for transparency for how the rewards program works on Modrinth. Feel
|
||||
free to join our Discord or email
|
||||
@@ -15,10 +15,11 @@
|
||||
<h2>Rewards Distribution</h2>
|
||||
<p>
|
||||
We collect ad revenue on our website and app through our ad network
|
||||
<a href="https://adrinth.com">Adrinth</a>. We then distribute this ad revenue to creators.
|
||||
<a href="https://adrinth.com">Adrinth</a>, which is powered by
|
||||
<a href="https://aditude.io">Aditude</a>. We then distribute this ad revenue to creators.
|
||||
</p>
|
||||
<p>
|
||||
The advertising revenue of the entire website and app is split 90% to creators and 10% to
|
||||
The advertising revenue of the entire website and app is split 75% to creators and 25% to
|
||||
Modrinth.
|
||||
</p>
|
||||
<p>
|
||||
@@ -42,10 +43,10 @@
|
||||
</ul>
|
||||
<p>In this scenario, the earnings for each creator and Modrinth would be as follows:</p>
|
||||
<ul>
|
||||
<li>Modrinth: $10 (10% of $100, the site's earnings for the day)</li>
|
||||
<li>User A: $58.69 ($90 * (10 + 30 + 100 + 10)/230)</li>
|
||||
<li>User B: $12.52 (0.4 * $90 * (50 + 20 + 10 + 0)/230)</li>
|
||||
<li>User C: $18.78 (0.6 * $90 * (50 + 20 + 10 + 0)/230)</li>
|
||||
<li>Modrinth: $25 (25% of $100, the site's earnings for the day)</li>
|
||||
<li>User A: $48.91 ($75 * (10 + 30 + 100 + 10)/230)</li>
|
||||
<li>User B: $10.43 (0.4 * $75 * (50 + 20 + 10 + 0)/230)</li>
|
||||
<li>User C: $15.65 (0.6 * $75 * (50 + 20 + 10 + 0)/230)</li>
|
||||
<li>Note: 230 is the sum of all page views and in-app downloads from above</li>
|
||||
</ul>
|
||||
<p>
|
||||
@@ -72,19 +73,62 @@
|
||||
</p>
|
||||
<h3>What methods can I use withdraw money from my account? Are there any fees?</h3>
|
||||
<p>
|
||||
Right now, you can use PayPal or Venmo to withdraw money from your Modrinth account. We are
|
||||
working on more methods to withdraw money from your account. There are fees to withdraw money
|
||||
from your Modrinth account—see the revenue page in your dashboard for more information.
|
||||
Right now, you can use PayPal or Venmo to withdraw money from your Modrinth account. Gift card
|
||||
withdrawal is also available. We are working on more methods to withdraw money from your
|
||||
account. There are fees to withdraw money from your Modrinth account—see the revenue page in
|
||||
your dashboard for more information.
|
||||
</p>
|
||||
<h3>Modrinth used to give 100% of project page revenue to creators. What changed?</h3>
|
||||
<h3 id="pending">What does "pending" revenue mean in my dashboard?</h3>
|
||||
<p>
|
||||
While this is true, our new system (as of 08/05/23) gives more of the site's revenue to
|
||||
creators, so creators will earn more. In the old system, we would earn revenue through
|
||||
advertisements in search and user profile pages. This amounted on average each month to about
|
||||
15-20% of the site's total advertising revenue (so a 80-85% split to creators). The new system
|
||||
gives creators more revenue and a more favorable split towards creators (90%).
|
||||
Modrinth receives ad revenue from our ad providers on a NET 60 day basis. Due to this, not all
|
||||
revenue is not immediately available to withdraw. We pay creators as soon as we receive the
|
||||
money from our ad providers, which is 60 days after the last day of each month. This table
|
||||
outlines some example dates of how NET 60 payments are made:
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Payment available date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>January 1st</td>
|
||||
<td>March 31st</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>January 15th</td>
|
||||
<td>March 31st</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>March 3rd</td>
|
||||
<td>May 30th</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>June 30th</td>
|
||||
<td>August 29th</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>July 14th</td>
|
||||
<td>September 29th</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>October 12th</td>
|
||||
<td>December 30th</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>How do I know Modrinth is being transparent about revenue?</h3>
|
||||
<p>
|
||||
We aim to be as transparent as possible with creator revenue. All of our code is open source,
|
||||
including our
|
||||
<a href="https://github.com/modrinth/labrinth/blob/master/src/queue/payouts.rs#L561">
|
||||
revenue distribution system </a
|
||||
>. We also have an
|
||||
<a href="https://api.modrinth.com/v3/payout/platform_revenue">API route</a> that allows users
|
||||
to query exact daily revenue for the site.
|
||||
</p>
|
||||
<h3></h3>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
border-radius: 1rem;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
</style>
|
||||
@@ -58,19 +58,18 @@
|
||||
]);
|
||||
});
|
||||
|
||||
let lastUrl = null
|
||||
window.addEventListener(
|
||||
"message",
|
||||
(event) => {
|
||||
if (event.data.modrinthOpenUrl && window.__TAURI_INTERNALS__ && lastUrl !== event.data.modrinthOpenUrl) {
|
||||
lastUrl = event.data.modrinthOpenUrl
|
||||
// window.__TAURI_INTERNALS__.invoke("plugin:shell|open", {
|
||||
// path: event.data.modrinthOpenUrl,
|
||||
// });
|
||||
if (event.data.modrinthAdClick && window.__TAURI_INTERNALS__) {
|
||||
window.__TAURI_INTERNALS__.invoke("plugin:ads|record_ads_click", {});
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
lastUrl = null
|
||||
}, 500)
|
||||
if (event.data.modrinthOpenUrl && window.__TAURI_INTERNALS__) {
|
||||
window.__TAURI_INTERNALS__.invoke("plugin:ads|open_link", {
|
||||
path: event.data.modrinthOpenUrl,
|
||||
origin: event.origin,
|
||||
});
|
||||
}
|
||||
},
|
||||
false,
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
<template>
|
||||
<div class="GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD">
|
||||
<a href="https://modrinth.com/plus" rel="noopener nofollow sponsored" :target="target"></a>
|
||||
<div class="GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-0">
|
||||
<div class="GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-1">
|
||||
<div class="GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-2">
|
||||
<ModrinthPlusIcon class="GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-3" />
|
||||
<span>
|
||||
<span>
|
||||
90% of ad revenue goes to creators. Go ad-free while supporting creators with
|
||||
</span>
|
||||
<strong>Modrinth Plus.</strong>
|
||||
<span> Subscribe today!</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-4">
|
||||
<a rel="noopener sponsored" :target="target" href="https://adrinth.com"> Ad via Adrinth </a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { ModrinthPlusIcon } from '@modrinth/assets'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
external: boolean
|
||||
}>(),
|
||||
{
|
||||
external: true,
|
||||
},
|
||||
)
|
||||
|
||||
const target = computed(() => (props.external ? '_blank' : '_self'))
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD {
|
||||
position: relative;
|
||||
margin-bottom: var(--gap-md);
|
||||
background: var(--color-ad);
|
||||
border: 1px solid var(--color-ad-raised);
|
||||
border-radius: var(--radius-lg);
|
||||
container-type: inline-size;
|
||||
width: 100%;
|
||||
|
||||
> a {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
&:has(> a:first-child:active) {
|
||||
scale: 0.99;
|
||||
}
|
||||
}
|
||||
.GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-0 {
|
||||
font-size: 14px;
|
||||
line-height: 1.3em;
|
||||
}
|
||||
.GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-1 {
|
||||
color: var(--color-base);
|
||||
padding: 1em;
|
||||
text-align: left;
|
||||
}
|
||||
.GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-2 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
color: var(--color-base);
|
||||
margin-right: 7.5rem;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
.GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-2 b,
|
||||
.GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-2 strong {
|
||||
color: var(--color-ad-highlight);
|
||||
}
|
||||
.GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-3 {
|
||||
padding-top: 1px;
|
||||
height: 1.5rem;
|
||||
width: auto;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-4 a {
|
||||
position: absolute;
|
||||
bottom: -1px;
|
||||
right: -1px;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.8em;
|
||||
color: var(--color-ad-contrast);
|
||||
background: var(--color-ad-raised);
|
||||
letter-spacing: 0.1ch;
|
||||
margin: 0;
|
||||
padding: 2px 10px;
|
||||
border-top-left-radius: var(--radius-lg);
|
||||
border-bottom-right-radius: var(--radius-lg);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 800px) {
|
||||
.GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-2 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-2 a {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -22,7 +22,6 @@ export { default as Page } from './base/Page.vue'
|
||||
export { default as Pagination } from './base/Pagination.vue'
|
||||
export { default as PopoutMenu } from './base/PopoutMenu.vue'
|
||||
export { default as ProjectCard } from './base/ProjectCard.vue'
|
||||
export { default as Promotion } from './base/Promotion.vue'
|
||||
export { default as ScrollablePanel } from './base/ScrollablePanel.vue'
|
||||
export { default as Slider } from './base/Slider.vue'
|
||||
export { default as StatItem } from './base/StatItem.vue'
|
||||
|
||||
Reference in New Issue
Block a user