Implement academy analytics, billing, and web stories updates

This commit is contained in:
2026-05-26 07:27:29 +02:00
parent 456c3d6bb0
commit 0b33a1b074
177 changed files with 27360 additions and 2685 deletions

View File

@@ -0,0 +1,55 @@
@extends('layouts.nova.content-layout')
@php
$hero_title = 'Skinbase Web Stories';
$hero_description = 'Explore visual stories from Skinbase Worlds, creator features, seasonal collections, and digital art highlights.';
@endphp
@section('page-content')
@if($stories->count() > 0)
<div class="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3">
@foreach($stories as $story)
<a href="{{ route('web-stories.show', ['slug' => $story->slug]) }}" class="group overflow-hidden rounded-[28px] border border-white/10 bg-white/[0.03] transition hover:bg-white/[0.06]">
<div class="aspect-[3/4] overflow-hidden bg-black/30">
@if($story->posterPortraitUrl())
<img src="{{ $story->posterPortraitUrl() }}" alt="{{ $story->title }}" class="h-full w-full object-cover transition duration-500 group-hover:scale-[1.03]" loading="lazy">
@else
<div class="flex h-full items-center justify-center text-white/20">
<i class="fa-solid fa-book-open-reader text-5xl"></i>
</div>
@endif
</div>
<div class="p-5">
<div class="text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-300/80">Web Story</div>
<h2 class="mt-3 line-clamp-2 text-xl font-semibold tracking-[-0.03em] text-white">{{ $story->title }}</h2>
@if($story->excerpt)
<p class="mt-3 line-clamp-3 text-sm leading-6 text-slate-300">{{ $story->excerpt }}</p>
@endif
<div class="mt-4 flex flex-wrap items-center gap-2 text-xs uppercase tracking-[0.16em] text-slate-400">
@if($story->world)
<span class="rounded-full border border-white/10 bg-white/[0.05] px-3 py-1">{{ $story->world->title }}</span>
@endif
@if($story->published_at)
<time datetime="{{ $story->published_at->toIso8601String() }}">{{ $story->published_at->format('M j, Y') }}</time>
@endif
</div>
<div class="mt-5 inline-flex items-center gap-2 text-sm font-semibold text-sky-300 transition group-hover:text-sky-200">
View Story
<i class="fa-solid fa-arrow-right text-xs"></i>
</div>
</div>
</a>
@endforeach
</div>
<div class="mt-10 flex justify-center">
{{ $stories->links() }}
</div>
@else
<div class="rounded-[28px] border border-white/10 bg-white/[0.03] px-8 py-14 text-center">
<div class="text-white/20"><i class="fa-solid fa-book-open-reader text-5xl"></i></div>
<h2 class="mt-4 text-xl font-semibold text-white">No Web Stories published yet</h2>
<p class="mt-3 text-sm leading-6 text-slate-300">Published Skinbase Web Stories will appear here once they are ready.</p>
</div>
@endif
@endsection

View File

@@ -0,0 +1,156 @@
<!doctype html>
<html amp lang="{{ app()->getLocale() }}">
<head>
<meta charset="utf-8">
<title>{{ $meta['title'] }}</title>
<link rel="canonical" href="{{ $meta['canonical'] }}">
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
<meta name="description" content="{{ $meta['description'] }}">
<meta name="robots" content="{{ $meta['robots'] }}">
<meta property="og:type" content="article">
<meta property="og:title" content="{{ $meta['og_title'] }}">
<meta property="og:description" content="{{ $meta['og_description'] }}">
<meta property="og:url" content="{{ $meta['og_url'] }}">
<meta property="og:image" content="{{ $meta['og_image'] }}">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ $meta['twitter_title'] }}">
<meta name="twitter:description" content="{{ $meta['twitter_description'] }}">
<meta name="twitter:image" content="{{ $meta['twitter_image'] }}">
<script async src="https://cdn.ampproject.org/v0.js"></script>
<script async custom-element="amp-story" src="https://cdn.ampproject.org/v0/amp-story-1.0.js"></script>
<script async custom-element="amp-video" src="https://cdn.ampproject.org/v0/amp-video-0.1.js"></script>
<style amp-boilerplate>
body {
-webkit-animation: -amp-start 8s steps(1,end) 0s 1 normal both;
-moz-animation: -amp-start 8s steps(1,end) 0s 1 normal both;
-ms-animation: -amp-start 8s steps(1,end) 0s 1 normal both;
animation: -amp-start 8s steps(1,end) 0s 1 normal both;
}
@-webkit-keyframes -amp-start { from { visibility: hidden; } to { visibility: visible; } }
@-moz-keyframes -amp-start { from { visibility: hidden; } to { visibility: visible; } }
@-ms-keyframes -amp-start { from { visibility: hidden; } to { visibility: visible; } }
@-o-keyframes -amp-start { from { visibility: hidden; } to { visibility: visible; } }
@keyframes -amp-start { from { visibility: hidden; } to { visibility: visible; } }
</style>
<noscript>
<style amp-boilerplate>
body {
-webkit-animation: none;
-moz-animation: none;
-ms-animation: none;
animation: none;
}
</style>
</noscript>
<style amp-custom>
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: #020617;
}
.story-text {
color: #ffffff;
padding: 32px;
text-shadow: 0 2px 18px rgba(0,0,0,.65);
display: flex;
flex-direction: column;
justify-content: flex-end;
min-height: 100%;
}
.story-text--top {
justify-content: flex-start;
}
.story-text--center {
justify-content: center;
}
.story-kicker {
font-size: 13px;
letter-spacing: .16em;
text-transform: uppercase;
opacity: .85;
}
.story-title {
font-size: 34px;
line-height: 1.05;
font-weight: 800;
margin-top: 8px;
}
.story-body {
font-size: 17px;
line-height: 1.35;
margin-top: 12px;
}
.story-cta {
display: inline-block;
margin-top: 18px;
padding: 11px 16px;
border-radius: 999px;
background: rgba(255,255,255,.92);
color: #111827;
font-weight: 700;
text-decoration: none;
}
.overlay {
background: linear-gradient(to top, rgba(0,0,0,.72), rgba(0,0,0,.12), rgba(0,0,0,.12));
}
.gradient-fill {
background: linear-gradient(180deg, rgba(15,23,42,0.95) 0%, rgba(14,165,233,0.35) 100%);
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<amp-story
standalone
title="{{ $story->title }}"
publisher="Skinbase"
publisher-logo-src="{{ $story->publisherLogoUrl() }}"
poster-portrait-src="{{ $story->posterPortraitUrl() }}"
@if($story->posterSquareUrl()) poster-square-src="{{ $story->posterSquareUrl() }}" @endif
>
@foreach($story->orderedPages->where('active', true) as $page)
<amp-story-page id="page-{{ $page->position }}">
<amp-story-grid-layer template="fill">
@if($page->background_type === \App\Models\WorldWebStoryPage::BACKGROUND_VIDEO && $page->backgroundUrl())
<amp-video autoplay loop muted layout="fill" poster="{{ $page->desktopBackgroundUrl() ?: $page->backgroundUrl() }}">
<source src="{{ $page->backgroundUrl() }}" type="video/mp4">
</amp-video>
@elseif($page->background_type === \App\Models\WorldWebStoryPage::BACKGROUND_GRADIENT || ! $page->backgroundUrl())
<div class="gradient-fill"></div>
@else
<amp-img
src="{{ $page->backgroundUrl() }}"
width="1080"
height="1920"
layout="responsive"
alt="{{ $page->alt_text ?: ($page->headline ?: $story->title) }}"
></amp-img>
@endif
</amp-story-grid-layer>
<amp-story-grid-layer template="fill">
<div class="overlay"></div>
</amp-story-grid-layer>
<amp-story-grid-layer template="vertical" class="story-text story-text--{{ $page->text_position ?: 'bottom' }}">
@if($page->caption)
<div class="story-kicker">{{ $page->caption }}</div>
@endif
@if($page->headline)
<h1 class="story-title">{{ $page->headline }}</h1>
@endif
@if($page->body)
<p class="story-body">{{ $page->body }}</p>
@endif
@if($page->cta_label && $page->cta_url)
<a class="story-cta" href="{{ $page->cta_url }}">{{ $page->cta_label }}</a>
@endif
</amp-story-grid-layer>
</amp-story-page>
@endforeach
</amp-story>
</body>
</html>