- {subscriber && (
-
-
- {subscribedTitle || "You're subscribed, thanks!"}
-
- {resource && (
-
- {subscribedSubtitle ||
- (session?.user
- ? common['newsletter-subscribed-logged-in']({
- resource,
- })
- : common['newsletter-subscribed-logged-out']({
- resource,
- }))}
-
- )}
-
- )}
-
-
- {!formId && (
-
- I respect your
- privacy. Unsubscribe at any time.
+ {subscriber && (
+
+
+ {subscribedTitle || "You're subscribed, thanks!"}
+
+ {resource && (
+
+ {subscribedSubtitle ||
+ (session?.user
+ ? common['newsletter-subscribed-logged-in']({
+ resource,
+ })
+ : common['newsletter-subscribed-logged-out']({
+ resource,
+ }))}
)}
+ )}
+
+
)
diff --git a/apps/craft-of-ui/src/components/video-block-newsletter-cta.tsx b/apps/craft-of-ui/src/components/video-block-newsletter-cta.tsx
index 5ac52e045..46e8f0485 100644
--- a/apps/craft-of-ui/src/components/video-block-newsletter-cta.tsx
+++ b/apps/craft-of-ui/src/components/video-block-newsletter-cta.tsx
@@ -2,7 +2,7 @@
import * as React from 'react'
import { useRouter } from 'next/navigation'
-import { redirectUrlBuilder, SubscribeToConvertkitForm } from '@/convertkit'
+import { SubscribeToConvertkitForm } from '@/convertkit'
import { Subscriber } from '@/schemas/subscriber'
import { track } from '@/utils/analytics'
import {
@@ -44,8 +44,7 @@ export const VideoBlockNewsletterCta: React.FC<
const handleOnSuccess = (subscriber: Subscriber | undefined) => {
if (subscriber) {
track(trackProps.event as string, trackProps.params)
- const redirectUrl = redirectUrlBuilder(subscriber, '/confirm')
- router.push(redirectUrl)
+ // No redirect - let the form handle inline success response
}
}
diff --git a/apps/craft-of-ui/src/convertkit/convertkit-subscribe-form.tsx b/apps/craft-of-ui/src/convertkit/convertkit-subscribe-form.tsx
index 834453198..01a32047a 100644
--- a/apps/craft-of-ui/src/convertkit/convertkit-subscribe-form.tsx
+++ b/apps/craft-of-ui/src/convertkit/convertkit-subscribe-form.tsx
@@ -1,9 +1,11 @@
-import React from 'react'
+import React, { useEffect, useRef, useState } from 'react'
import Spinner from '@/components/spinner'
import { useConvertkitForm } from '@/hooks/use-convertkit-form'
import { type Subscriber } from '@/schemas/subscriber'
import { api } from '@/trpc/react'
+import { cn } from '@/utils/cn'
import { CK_SUBSCRIBER_KEY } from '@skillrecordings/config'
+import { LockIcon, ShieldCheckIcon } from 'lucide-react'
import queryString from 'query-string'
import * as Yup from 'yup'
@@ -97,6 +99,7 @@ export const SubscribeToConvertkitForm: React.FC<
showMascot,
...rest
}) => {
+ const [isEmailFocused, setIsEmailFocused] = useState(false)
const {
isSubmitting,
status,
@@ -115,176 +118,323 @@ export const SubscribeToConvertkitForm: React.FC<
validateOnChange,
})
+ // Check if bear should show: focused AND has content
+ const shouldShowBear =
+ isEmailFocused && values.email && values.email.trim().length > 0
+
return (
-
+ {isSubmitting ?
loading...
: null}
+
)
}
export default SubscribeToConvertkitForm
-
-export const redirectUrlBuilder = (
- subscriber: Subscriber,
- path: string,
- queryParams?: {
- [key: string]: string
- },
-) => {
- const url = queryString.stringifyUrl({
- url: path,
- query: {
- [CK_SUBSCRIBER_KEY]: subscriber.id,
- email: subscriber.email_address,
- ...queryParams,
- },
- })
- return url
-}
diff --git a/apps/craft-of-ui/src/convertkit/index.ts b/apps/craft-of-ui/src/convertkit/index.ts
index dac61bfe3..ae5866dcf 100644
--- a/apps/craft-of-ui/src/convertkit/index.ts
+++ b/apps/craft-of-ui/src/convertkit/index.ts
@@ -1,5 +1,3 @@
-import SubscribeToConvertkitForm, {
- redirectUrlBuilder,
-} from './convertkit-subscribe-form'
+import SubscribeToConvertkitForm from './convertkit-subscribe-form'
-export { SubscribeToConvertkitForm, redirectUrlBuilder }
+export { SubscribeToConvertkitForm }
diff --git a/apps/craft-of-ui/src/styles/globals.css b/apps/craft-of-ui/src/styles/globals.css
index f29a884bf..1d292cb8f 100644
--- a/apps/craft-of-ui/src/styles/globals.css
+++ b/apps/craft-of-ui/src/styles/globals.css
@@ -28,7 +28,7 @@
@layer base {
:root {
- --nav-height: 5rem;
+ --nav-height: 80px;
--command-bar-height: 2.25rem;
--pane-layout-height: calc(
100vh - var(--nav-height) - var(--command-bar-height)
@@ -36,7 +36,7 @@
--code-editor-layout-height: calc(
100vh - var(--nav-height) - var(--command-bar-height) - 30px
);
- --background: 0 0% 93%;
+ --background: 0 0% 98%;
--foreground: 240 2% 20%;
--card: 0 0% 100%;
--card-foreground: 240 2% 20%;
@@ -63,6 +63,34 @@
--chart-5: 27 87% 67%;
color-scheme: light;
+ /* imported */
+ --header-height: 80px;
+ --content: 600px;
+ --gutter: 1.5rem;
+ --font-size-min: 14;
+ --font-size-max: 18;
+ --font-ratio-min: 1.2;
+ --font-ratio-max: 1.33;
+ --font-width-min: 375;
+ --font-width-max: 1500;
+ --breakpoint-lg: 968px;
+ --color-red-400: oklch(70.4% 0.191 22.216);
+ --color-red-500: #ef4444;
+ --color-red-600: #ef4444;
+ --color-red-800: #991b1b;
+ --red-600: #ef4444;
+ --color-green-200: oklch(0.925 0.084 155.995);
+ --color-green-600: oklch(0.627 0.194 149.214);
+ --color-green-800: oklch(0.448 0.119 151.328);
+ --color-gray-100: oklch(0.967 0.003 264.542);
+ --color-gray-200: oklch(0.928 0.006 264.531);
+ --color-gray-300: oklch(0.872 0.01 258.338);
+ --color-gray-400: oklch(0.707 0.022 261.325);
+ --color-gray-500: oklch(0.551 0.027 264.364);
+ --color-gray-800: oklch(0.278 0.033 256.848);
+ --color-gray-900: oklch(0.21 0.034 264.665);
+ --color-white: #fff;
+
/* codehike theme */
--ch-0: light;
--ch-1: #6e7781;
@@ -176,13 +204,19 @@
}
body {
- @apply bg-background text-foreground selection:bg-primary selection:text-primary-foreground overflow-x-hidden font-normal antialiased;
+ background: color-mix(in hsl, #fff, hsl(45 80% 50%) 1%);
+ @apply selection:bg-primary selection:text-primary-foreground overflow-x-hidden font-[300] text-[canvasText] antialiased;
+ color: color-mix(in hsl, canvasText, canvas 40%);
font-feature-settings:
'rlig' 1,
'calt' 1;
}
- .home-page-grid::before {
+ .dark body {
+ background: hsl(210 10% 10%);
+ }
+
+ body::before {
--size: 40px;
--line: color-mix(in hsl, canvasText, transparent 90%);
content: '';
@@ -201,10 +235,76 @@
z-index: -1;
}
+ .text-fluid {
+ --fluid-min: calc(
+ var(--font-size-min) * pow(var(--font-ratio-min), var(--font-level, 0))
+ );
+ --fluid-max: calc(
+ var(--font-size-max) * pow(var(--font-ratio-max), var(--font-level, 0))
+ );
+ --fluid-preferred: calc(
+ (var(--fluid-max) - var(--fluid-min)) /
+ (var(--font-width-max) - var(--font-width-min))
+ );
+ --fluid-type: clamp(
+ (var(--fluid-min) / 16) * 1rem,
+ ((var(--fluid-min) / 16) * 1rem) -
+ (((var(--fluid-preferred) * var(--font-width-min)) / 16) * 1rem) +
+ (var(--fluid-preferred) * var(--variable-unit, 100vi)),
+ (var(--fluid-max) / 16) * 1rem
+ );
+ font-size: var(--fluid-type);
+ }
+ .mask-content {
+ mask-image:
+ linear-gradient(
+ #0000 var(--start),
+ #fff var(--end) calc(100% - var(--header-height)),
+ #0000
+ ),
+ linear-gradient(#fff 0 100%);
+ mask-position:
+ 0 50%,
+ calc(100vw - var(--gutter)) 0;
+ mask-size: calc(100vw - var(--gutter)) 100%;
+ mask-repeat: no-repeat;
+ }
+
html {
/* scrollbar-gutter: stable; */
}
+ :where(a, button):focus-visible {
+ outline: 2px solid var(--color-red-400);
+ outline-offset: 2px;
+ }
+
+ strong {
+ @apply font-[600];
+ }
+
+ ::selection {
+ background: oklch(70.4% 0.191 22.216) !important;
+ color: #fff !important;
+ }
+
+ ::-moz-selection {
+ background: oklch(70.4% 0.191 22.216) !important;
+ color: #fff !important;
+ }
+
+ html[lang] {
+ color-scheme: light dark;
+ scrollbar-color: light-dark(var(--color-red-400), var(--color-red-400))
+ #0000;
+ scrollbar-width: thin;
+ }
+ @media (prefers-reduced-motion: no-preference) {
+ .overflow-auto {
+ scroll-behavior: smooth;
+ }
+ }
+
html.dark .shiki,
html.dark .shiki span {
color: var(--shiki-dark) !important;
@@ -215,7 +315,7 @@
text-decoration: var(--shiki-dark-text-decoration) !important;
}
- /*
+ /*
Hide the second #primary-newsletter-cta only if there are exactly two on the page.
This is a temporary workaround for duplicate IDs rendered by the framework.
Ideally, IDs should be unique—consider refactoring to use class names or unique IDs.
@@ -235,3 +335,108 @@
list-style-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTQiIGhlaWdodD0iMTQiIHZpZXdCb3g9IjAgMCAxNCAxNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE0IDMuMTVMMTMuMyAxLjc1QzcuMjYyIDMuNSA0LjIgNy4zNSA0LjIgNy4zNUwxLjQgNS4yNUwwIDYuNjVMNC4yIDEyLjI1QzcuNDM3IDYuMjEzIDE0IDMuMTUgMTQgMy4xNVoiIGZpbGw9IiM3QzNCRUQiLz4KPC9zdmc+Cg==');
}
}
+
+@layer signature {
+ .sig {
+ --duration: 1.4;
+ --delay: 0.2;
+ --base-delay: calc((var(--duration) + var(--delay)));
+ --natty-delay: 0.973125;
+ width: 160px;
+ rotate: 10deg;
+ path {
+ --end: 1.025;
+ stroke-dasharray: var(--end);
+ stroke-dashoffset: var(--end);
+ animation: draw calc(var(--path-speed) * 1s)
+ calc((var(--base-delay) * 1s) + var(--path-delay, 0) * 1s) ease-in
+ forwards;
+ }
+
+ :is(.eye, .nose) {
+ fill: hsl(0 0% 0% / 0);
+ animation:
+ draw calc(var(--path-speed) * 1s)
+ calc((var(--base-delay) * 1s) + var(--path-delay, 0) * 1s) ease-in
+ forwards,
+ fill 0.5s calc((var(--base-delay) * 1s) + var(--natty-delay, 0) * 1s)
+ forwards;
+ }
+ .eye {
+ transform-box: fill-box;
+ transform-origin: 50% 50%;
+ animation:
+ draw calc(var(--path-speed) * 1s)
+ calc((var(--base-delay) * 1s) + var(--path-delay, 0) * 1s) ease-in
+ forwards,
+ fill 0.5s calc((var(--base-delay) * 1s) + var(--natty-delay, 0) * 1s)
+ forwards,
+ blink 6s calc((var(--base-delay) * 1s) + var(--natty-delay, 0) * 1s)
+ infinite;
+ }
+ }
+
+ @keyframes fill {
+ to {
+ fill: currentColor;
+ }
+ }
+
+ @keyframes draw {
+ to {
+ stroke-dashoffset: 0;
+ }
+ }
+
+ @keyframes blink {
+ 0%,
+ 46%,
+ 48%,
+ 50%,
+ 100% {
+ transform: scaleY(1);
+ }
+ 47%,
+ 49% {
+ transform: scaleY(0.1);
+ }
+ }
+}
+
+@keyframes move {
+ 0% {
+ transform: translateX(0);
+ }
+ 100% {
+ transform: translateX(100%);
+ }
+}
+
+@keyframes float {
+ to {
+ translate: 0 calc(-100% + -100vh);
+ }
+}
+
+@keyframes bear-float-up {
+ 0% {
+ transform: translateY(100%);
+ opacity: 1;
+ }
+ 80% {
+ opacity: 1;
+ }
+ 100% {
+ transform: translateY(-200vh);
+ opacity: 0;
+ }
+}
+
+.animate-bear-float-up {
+ animation: bear-float-up 10s cubic-bezier(0.4, 0, 0.2, 1) forwards;
+}
+
+/* Ensure red button background is applied correctly */
+.bg-red-600 {
+ background-color: var(--red-600, oklch(0.637 0.237 25.331));
+}
diff --git a/apps/craft-of-ui/src/styles/primary-newsletter-cta.css b/apps/craft-of-ui/src/styles/primary-newsletter-cta.css
index 2b5bdf286..c2caa5aca 100644
--- a/apps/craft-of-ui/src/styles/primary-newsletter-cta.css
+++ b/apps/craft-of-ui/src/styles/primary-newsletter-cta.css
@@ -5,8 +5,8 @@
@apply mb-0;
}
input {
- @apply placeholder:text-muted-foreground text-foreground relative flex p-5 text-base placeholder:opacity-75;
- @apply before:bg-linear-to-r before:absolute before:left-0 before:top-0 before:h-px before:w-full before:from-transparent before:via-white before:to-transparent;
+ @apply placeholder:text-muted-foreground text-foreground relative flex text-base placeholder:opacity-75;
+ @apply before:absolute before:left-0 before:top-0 before:h-px before:w-full before:bg-gradient-to-r before:from-transparent before:via-white before:to-transparent;
}
[data-sr-input-label] {
@apply text-muted-foreground mt-3 inline-block pb-1 text-base font-normal after:content-[':'];
diff --git a/apps/craft-of-ui/tailwind.config.ts b/apps/craft-of-ui/tailwind.config.ts
index db92b3a74..bac65f9d2 100644
--- a/apps/craft-of-ui/tailwind.config.ts
+++ b/apps/craft-of-ui/tailwind.config.ts
@@ -69,6 +69,17 @@ module.exports = {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))',
},
+ red: {
+ 400: '#f87171',
+ 500: '#ef4444',
+ 600: '#ef4444',
+ 800: '#991b1b',
+ },
+ green: {
+ 200: 'var(--color-green-200)',
+ 600: 'var(--color-green-600)',
+ 800: 'var(--color-green-800)',
+ },
},
borderRadius: {
lg: `var(--radius)`,
@@ -93,11 +104,41 @@ module.exports = {
'0%': { 'background-position': '100%' },
'100%': { 'background-position': '-100%' },
},
+ // Add missing animations that might be used
+ blink: {
+ '0%, 46%, 48%, 50%, 100%': { transform: 'scaleY(1)' },
+ '47%, 49%': { transform: 'scaleY(0.1)' },
+ },
+ draw: {
+ to: { strokeDashoffset: 0 },
+ },
+ fill: {
+ to: { fill: 'currentColor' },
+ },
+ move: {
+ '0%': { transform: 'translateX(0)' },
+ '100%': { transform: 'translateX(100%)' },
+ },
+ 'bear-float-up': {
+ '0%': { transform: 'translateY(100%)', opacity: 1 },
+ '80%': { opacity: 1 },
+ '100%': { transform: 'translateY(-200vh)', opacity: 0 },
+ },
+ float: {
+ to: { translate: '0 calc(-100% + -100vh)' },
+ },
},
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out',
shine: 'shine 5s linear infinite',
+ blink: 'blink 6s infinite',
+ draw: 'draw var(--path-speed, 1s) ease-in forwards',
+ fill: 'fill 0.5s forwards',
+ move: 'move 1s ease-in-out',
+ 'bear-float-up':
+ 'bear-float-up 10s cubic-bezier(0.4, 0, 0.2, 1) forwards',
+ float: 'float var(--duration, 15s) linear 2s forwards',
},
typography: (theme: any) => ({
DEFAULT: {