feat: update select plan UI

pull/8731/head
Aaron Iker 2026-01-15 17:52:59 +01:00
parent cf4fe5dc82
commit ad33807627
3 changed files with 111 additions and 25 deletions

View File

@ -1,3 +1,72 @@
::view-transition-group(*) {
animation-duration: 200ms;
animation-timing-function: cubic-bezier(0.25, 0, 0.5, 1);
}
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 200ms;
animation-timing-function: cubic-bezier(0.25, 0, 0.5, 1);
}
@keyframes reveal {
from {
mask-position: 0% 200%;
opacity: 0;
}
to {
mask-position: 0% 50%;
opacity: 1;
}
}
@keyframes reveal-reverse {
from {
mask-position: 0% 50%;
opacity: 1;
}
to {
mask-position: 0% 200%;
opacity: 0;
}
}
@keyframes fade-in {
from {
opacity: 0;
transform: translateY(4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
::view-transition-new(terms) {
animation: reveal 400ms cubic-bezier(0.25, 0, 0.5, 1) forwards;
}
::view-transition-old(terms) {
animation: reveal-reverse 200ms cubic-bezier(0.25, 0, 0.5, 1) forwards;
}
::view-transition-new(action) {
animation: fade-in 300ms cubic-bezier(0.25, 0, 0.5, 1) forwards;
}
::view-transition-old(action) {
animation: fade-out 150ms cubic-bezier(0.25, 0, 0.5, 1) forwards;
}
[data-page="black"] {
background: #000;
min-height: 100vh;
@ -8,14 +77,18 @@
font-family: var(--font-mono);
color: #fff;
[data-component="header-gradient"] {
[data-component="header-logo"] {
filter: drop-shadow(0 8px 24px rgba(0, 0, 0, 0.25)) drop-shadow(0 4px 16px rgba(0, 0, 0, 0.1));
position: relative;
z-index: 1;
}
.header-light-rays {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 288px;
inset: 0 0 auto 0;
height: 30dvh;
pointer-events: none;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.075) 0%, rgba(0, 0, 0, 0) 100%);
z-index: 0;
}
[data-component="header"] {
@ -210,7 +283,7 @@
display: flex;
flex-direction: column;
gap: 12px;
padding: 20px;
padding: 24px;
border: 1px solid rgba(255, 255, 255, 0.17);
border-radius: 4px;
text-decoration: none;
@ -269,7 +342,7 @@
display: flex;
flex-direction: column;
gap: 12px;
padding: 20px;
padding: 24px;
border: 1px solid rgba(255, 255, 255, 0.17);
border-radius: 4px;
width: 100%;
@ -314,6 +387,10 @@
flex-direction: column;
gap: 8px;
text-align: left;
mask-image: linear-gradient(to bottom, transparent, black 25% 75%, transparent);
mask-repeat: no-repeat;
mask-position: 0% 50%;
mask-size: 100% 200%;
li {
color: rgba(255, 255, 255, 0.59);
@ -533,6 +610,7 @@
text-align: center;
font-size: 13px;
font-style: italic;
view-transition-name: fine-print;
a {
color: rgba(255, 255, 255, 0.39);

View File

@ -1,8 +1,9 @@
import { A, createAsync, RouteSectionProps } from "@solidjs/router"
import { Title, Meta, Link } from "@solidjs/meta"
import { createMemo } from "solid-js"
import { createMemo, createSignal } from "solid-js"
import { github } from "~/lib/github"
import { config } from "~/config"
import LightRays, { defaultConfig, LightRaysControls, type LightRaysConfig } from "~/component/light-rays"
import "./black.css"
export default function BlackLayout(props: RouteSectionProps) {
@ -16,6 +17,8 @@ export default function BlackLayout(props: RouteSectionProps) {
: config.github.starsFormatted.compact,
)
const [lightRaysConfig, setLightRaysConfig] = createSignal<LightRaysConfig>(defaultConfig)
return (
<div data-page="black">
<Title>OpenCode Black | Access all the world's best coding models</Title>
@ -39,7 +42,10 @@ export default function BlackLayout(props: RouteSectionProps) {
content="Get access to Claude, GPT, Gemini and more with OpenCode Black subscription plans."
/>
<Meta name="twitter:image" content="/social-share-black.png" />
<div data-component="header-gradient" />
<LightRays config={lightRaysConfig} class="header-light-rays" />
<LightRaysControls config={lightRaysConfig} setConfig={setLightRaysConfig} />
<header data-component="header">
<A href="/" data-component="header-logo">
<svg xmlns="http://www.w3.org/2000/svg" width="179" height="32" viewBox="0 0 179 32" fill="none">

View File

@ -43,11 +43,16 @@ export default function Black() {
<div data-slot="pricing">
<For each={plans}>
{(plan) => (
<button type="button" onClick={() => setSelected(plan.id)} data-slot="pricing-card">
<div data-slot="icon">
<button
type="button"
onClick={() => select(plan.id)}
data-slot="pricing-card"
style={{ "view-transition-name": `plan-card-${plan.id}` }}
>
<div data-slot="icon" style={{ "view-transition-name": `plan-icon-${plan.id}` }}>
<PlanIcon plan={plan.id} />{" "}
</div>
<p data-slot="price">
<p data-slot="price" style={{ "view-transition-name": `plan-price-${plan.id}` }}>
<span data-slot="amount">${plan.id}</span> <span data-slot="period">per month</span>
<Show when={plan.multiplier}>
<span data-slot="multiplier">{plan.multiplier}</span>
@ -57,25 +62,22 @@ export default function Black() {
)}
</For>
</div>
<p data-slot="fine-print">
Prices shown don't include applicable tax · <A href="/legal/terms-of-service">Terms of Service</A>
</p>
</Match>
<Match when={selectedPlan()}>
{(plan) => (
<div data-slot="selected-plan">
<div data-slot="selected-card">
<div data-slot="icon">
<div data-slot="selected-card" style={{ "view-transition-name": `plan-card-${plan().id}` }}>
<div data-slot="icon" style={{ "view-transition-name": `plan-icon-${plan().id}` }}>
<PlanIcon plan={plan().id} />
</div>
<p data-slot="price">
<p data-slot="price" style={{ "view-transition-name": `plan-price-${plan().id}` }}>
<span data-slot="amount">${plan().id}</span>{" "}
<span data-slot="period">per person billed monthly</span>
<Show when={plan().multiplier}>
<span data-slot="multiplier">{plan().multiplier}</span>
</Show>
</p>
<ul data-slot="terms">
<ul data-slot="terms" style={{ "view-transition-name": "terms" }}>
<li>Your subscription will not start immediately</li>
<li>You will be added to the waitlist and activated soon</li>
<li>Your card will be only charged when your subscription is activated</li>
@ -84,8 +86,8 @@ export default function Black() {
<li>Limits may be adjusted and plans may be discontinued in the future</li>
<li>Cancel your subscription at anytime</li>
</ul>
<div data-slot="actions">
<button type="button" onClick={() => setSelected(null)} data-slot="cancel">
<div data-slot="actions" style={{ "view-transition-name": "action" }}>
<button type="button" onClick={() => cancel()} data-slot="cancel">
Cancel
</button>
<a href={`/black/subscribe/${plan().id}`} data-slot="continue">
@ -93,13 +95,13 @@ export default function Black() {
</a>
</div>
</div>
<p data-slot="fine-print">
Prices shown don't include applicable tax · <A href="/legal/terms-of-service">Terms of Service</A>
</p>
</div>
)}
</Match>
</Switch>
<p data-slot="fine-print" style={{ "view-transition-name": "fine-print" }}>
Prices shown don't include applicable tax · <A href="/legal/terms-of-service">Terms of Service</A>
</p>
</section>
</>
)