feat: certificates, landing pages, ai tutor, website update, compliance
This commit is contained in:
@@ -1,52 +1,172 @@
|
||||
<script lang="ts">
|
||||
import type { OrgLandingPageProps, OrgLandingPageTheme } from './types';
|
||||
import type { OrgLandingPageFooterConfig, OrgLandingPageTheme } from './types';
|
||||
import { getFooterTokens } from './landing-page-footer.tokens';
|
||||
import FooterSocialIcon from './footer-social-icon.svelte';
|
||||
import ArrowRightIcon from '@lucide/svelte/icons/arrow-right';
|
||||
import { BlurFade } from '../animation/blurfade';
|
||||
import EditableLandingSection from './editable-section.svelte';
|
||||
|
||||
interface Props {
|
||||
orgName: string;
|
||||
logoUrl?: string;
|
||||
footerLinks: OrgLandingPageProps['footerLinks'];
|
||||
footerText: string;
|
||||
footer: OrgLandingPageFooterConfig;
|
||||
variant: OrgLandingPageTheme;
|
||||
}
|
||||
|
||||
let { orgName, logoUrl, footerLinks, footerText, variant }: Props = $props();
|
||||
let { orgName, logoUrl, footer, variant }: Props = $props();
|
||||
|
||||
const tokens = $derived(getFooterTokens(variant));
|
||||
|
||||
const bottom = $derived(footer.bottom);
|
||||
const bottomLinks = $derived(bottom?.links ?? []);
|
||||
const bottomText = $derived((bottom?.text ?? '').trim());
|
||||
|
||||
function isGridMode(config: OrgLandingPageFooterConfig): boolean {
|
||||
const tagline = (config.brand.tagline ?? '').trim();
|
||||
const copyright = (config.brand.copyright ?? '').trim();
|
||||
|
||||
return config.columns.length > 0 || config.brand.socials.length > 0 || Boolean(tagline) || Boolean(copyright);
|
||||
}
|
||||
|
||||
const showGrid = $derived(isGridMode(footer));
|
||||
|
||||
const showBottomRowInGrid = $derived(bottomText.length > 0 || bottomLinks.length > 0);
|
||||
|
||||
function hasCta(column: OrgLandingPageFooterConfig['columns'][number]): boolean {
|
||||
const cta = column.cta;
|
||||
if (!cta) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Boolean(cta.label.trim() && cta.href.trim());
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if variant === 'minimal'}
|
||||
<footer
|
||||
class="ui:py-12 ui:px-8 ui:max-w-[1200px] ui:mx-auto ui:border-t ui:border-border/50 ui:flex ui:flex-col ui:md:flex-row ui:items-center ui:justify-between ui:gap-4 ui:text-sm ui:text-muted-foreground"
|
||||
>
|
||||
<a href="/" class="ui:font-semibold ui:text-xl ui:text-foreground ui:flex ui:items-center ui:gap-2 ui:no-underline">
|
||||
{#if logoUrl}
|
||||
<img src={logoUrl} alt={orgName} class="ui:h-8 ui:w-auto" />
|
||||
{/if}
|
||||
{orgName}
|
||||
</a>
|
||||
<div class="ui:flex ui:flex-wrap ui:justify-center ui:gap-8">
|
||||
{#each footerLinks as link}
|
||||
<a href={link.href} class="ui:hover:text-foreground ui:transition-colors">{link.label}</a>
|
||||
{/each}
|
||||
</div>
|
||||
<p>{footerText}</p>
|
||||
</footer>
|
||||
{:else}
|
||||
<footer class="ui:bg-foreground ui:text-background/60 ui:py-16 ui:px-6">
|
||||
<div class="ui:max-w-7xl ui:mx-auto ui:flex ui:flex-col ui:md:flex-row ui:items-center ui:justify-between ui:gap-8">
|
||||
<a
|
||||
href="/"
|
||||
class="ui:font-black ui:text-2xl ui:text-background ui:tracking-tighter ui:flex ui:items-center ui:gap-3 ui:no-underline"
|
||||
>
|
||||
{#if logoUrl}
|
||||
<img src={logoUrl} alt={orgName} class="ui:h-8 ui:w-auto ui:grayscale ui:brightness-200" />
|
||||
<EditableLandingSection sectionKey="footer">
|
||||
<footer class={tokens.root}>
|
||||
{#if variant === 'terminal'}
|
||||
<div
|
||||
class="ui:pointer-events-none ui:absolute ui:inset-x-[-10%] ui:bottom-[-50%] ui:h-[380px]"
|
||||
style="
|
||||
background:
|
||||
radial-gradient(ellipse 60% 80% at 30% 50%, color-mix(in oklab, #f59e0b 35%, transparent) 0%, transparent 60%),
|
||||
radial-gradient(ellipse 50% 60% at 70% 60%, color-mix(in oklab, #ef4444 30%, transparent) 0%, transparent 60%),
|
||||
radial-gradient(ellipse 40% 50% at 50% 80%, color-mix(in oklab, #c026d3 22%, transparent) 0%, transparent 60%);
|
||||
filter: blur(40px);
|
||||
"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
{/if}
|
||||
<div class="ui:relative {tokens.container}">
|
||||
{#if showGrid}
|
||||
{#snippet gridInner()}
|
||||
<div
|
||||
class="ui:grid ui:grid-cols-1 ui:gap-y-10 ui:gap-x-8 ui:sm:grid-cols-2 ui:md:gap-x-12 ui:md:gap-y-12 ui:md:grid-cols-[minmax(0,1.4fr)_repeat(auto-fit,minmax(140px,1fr))]"
|
||||
>
|
||||
<div class="ui:sm:col-span-2 ui:md:col-span-1 ui:min-w-0">
|
||||
<a href="/" class={tokens.brandName}>
|
||||
{#if logoUrl}
|
||||
<img src={logoUrl} alt="" class={tokens.logoImg} />
|
||||
{/if}
|
||||
<span class="ui:break-words">{orgName}</span>
|
||||
</a>
|
||||
{#if footer.brand.tagline?.trim()}
|
||||
<p class={tokens.brandTagline}>{footer.brand.tagline}</p>
|
||||
{/if}
|
||||
{#if footer.brand.socials.length > 0}
|
||||
<ul class={tokens.socialWrap}>
|
||||
{#each footer.brand.socials as social (social.href + social.platform)}
|
||||
<li class="ui:list-none">
|
||||
<a
|
||||
href={social.href}
|
||||
class={tokens.socialIcon}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
aria-label={social.platform}
|
||||
>
|
||||
<FooterSocialIcon platform={social.platform} class="ui:size-4 ui:md:size-[1.125rem]" />
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
{#if footer.brand.copyright?.trim()}
|
||||
<p class="{tokens.brandTagline} ui:mt-3">{footer.brand.copyright}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#each footer.columns as column (column.id)}
|
||||
<div class="ui:min-w-0">
|
||||
{#if column.heading.trim()}
|
||||
<h3 class={tokens.columnHeading}>{column.heading}</h3>
|
||||
{/if}
|
||||
<ul class="ui:list-none ui:m-0 ui:p-0 ui:space-y-1">
|
||||
{#each column.links as link (link.id)}
|
||||
<li>
|
||||
<a href={link.href} class={tokens.columnLink}>{link.label}</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{#if hasCta(column)}
|
||||
<a href={column.cta!.href} class={tokens.columnCta}>
|
||||
{column.cta!.label}
|
||||
<ArrowRightIcon class="ui:size-4 ui:shrink-0" aria-hidden="true" />
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
{#if variant === 'minimal' || variant === 'bold' || variant === 'saas' || variant === 'tech' || variant === 'studio' || variant === 'corporate' || variant === 'terminal'}
|
||||
<BlurFade delay={0} once={true}>
|
||||
{#snippet children()}
|
||||
{@render gridInner()}
|
||||
{/snippet}
|
||||
</BlurFade>
|
||||
{:else}
|
||||
{@render gridInner()}
|
||||
{/if}
|
||||
{orgName}
|
||||
</a>
|
||||
<div class="ui:flex ui:flex-wrap ui:justify-center ui:gap-8">
|
||||
{#each footerLinks as link}
|
||||
<a href={link.href} class="ui:hover:text-background ui:font-medium ui:transition-colors">{link.label}</a>
|
||||
{/each}
|
||||
</div>
|
||||
<p class="ui:text-sm">{footerText}</p>
|
||||
|
||||
{#if showBottomRowInGrid}
|
||||
{#if variant === 'classic' && tokens.bottomHairline}
|
||||
<div class={tokens.bottomHairline} aria-hidden="true"></div>
|
||||
{/if}
|
||||
<div class={tokens.bottomRow}>
|
||||
{#if bottomText}
|
||||
<p class={tokens.bottomText}>{bottomText}</p>
|
||||
{/if}
|
||||
{#if bottomLinks.length > 0}
|
||||
<div class={tokens.bottomLinksWrap}>
|
||||
{#each bottomLinks as link (link.id)}
|
||||
<a href={link.href} class={tokens.bottomLink}>{link.label}</a>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class={tokens.compactRow}>
|
||||
<a href="/" class={tokens.brandName}>
|
||||
{#if logoUrl}
|
||||
<img src={logoUrl} alt="" class={tokens.logoImg} />
|
||||
{/if}
|
||||
<span class="ui:break-words">{orgName}</span>
|
||||
</a>
|
||||
{#if bottomLinks.length > 0}
|
||||
<div
|
||||
class="ui:flex ui:flex-wrap ui:justify-start ui:gap-6 ui:md:justify-center ui:md:gap-8 ui:w-full ui:md:w-auto"
|
||||
>
|
||||
{#each bottomLinks as link (link.id)}
|
||||
<a href={link.href} class={tokens.bottomLink}>{link.label}</a>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{#if bottomText}
|
||||
<p class="{tokens.bottomText} ui:w-full ui:md:w-auto ui:text-left ui:md:text-right">{bottomText}</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</footer>
|
||||
{/if}
|
||||
</EditableLandingSection>
|
||||
|
||||
Reference in New Issue
Block a user