Migrate components

This commit is contained in:
Beau Findlay
2026-01-31 15:51:29 +00:00
parent ee136857d1
commit 2105d3b85d
32 changed files with 2003 additions and 692 deletions

View File

@@ -1,10 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net10.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException> <BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<_ContentIncludedByDefault Remove="Components\Shared\Icon.razor"/>
</ItemGroup>
</Project> </Project>

View File

@@ -0,0 +1,25 @@
@* External link component *@
<a href="@Href"
target="@Target"
class="@CombinedClasses">@ChildContent</a>
@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
[Parameter]
public string? Href { get; set; }
[Parameter]
public string Target { get; set; } = "_blank";
[Parameter]
public string? CssClass { get; set; }
private string CombinedClasses => string.IsNullOrEmpty(CssClass)
? "underline underline-offset-2"
: $"underline underline-offset-2 {CssClass}";
}

View File

@@ -2,19 +2,66 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport"
<base href="/" /> content="width=device-width, initial-scale=1.0"/>
<ResourcePreloader />
<link rel="stylesheet" href="css/app.css" /> <base href="/"/>
<ImportMap />
<link rel="icon" type="image/png" href="favicon.png" /> <ResourcePreloader/>
<HeadOutlet />
<!-- Styles -->
<link rel="stylesheet"
href="css/app.css"/>
<ImportMap/>
<!-- Standard Favicons -->
<link rel="icon"
type="image/x-icon"
href="images/favicon.ico"/>
<link rel="icon"
type="image/png"
sizes="16x16"
href="images/favicon-16x16.png"/>
<link rel="icon"
type="image/png"
sizes="32x32"
href="images/favicon-32x32.png"/>
<!-- Apple Touch Icons -->
<link rel="apple-touch-icon"
sizes="180x180"
href="images/apple-touch-icon.png"/>
<!-- Android/Chrome -->
<link rel="manifest"
href="site.webmanifest"/>
<!-- Microsoft Tiles -->
<meta name="msapplication-TileColor"
content="#1a1a1a"/>
<meta name="msapplication-config"
content="browserconfig.xml"/>
<!-- Theme Color -->
<meta name="theme-color"
content="#1a1a1a"/>
<!-- Open Graph / Social Sharing -->
<meta property="og:image"
content="images/og-image.png"/>
<meta property="og:image:width"
content="1200"/>
<meta property="og:image:height"
content="630"/>
<HeadOutlet/>
</head> </head>
<body> <body>
<Routes /> <Routes/>
<script src="@Assets["_framework/blazor.web.js"]"></script> <script src="@Assets["_framework/blazor.web.js"]"></script>
</body> </body>
</html> </html>

View File

@@ -0,0 +1,10 @@
@* Contact call-to-action *@
<div class="contact-section">
<Text>If you think I can help with your project...</Text>
<a href="mailto:me@beaufindlay.com"
class="contact-button">
Get in touch
<Icon Type="IconType.SendArrow"/>
</a>
</div>

View File

@@ -0,0 +1,125 @@
@switch (Type)
{
case IconType.Github:
<svg viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
</svg>
break;
case IconType.LinkedIn:
<svg viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg">
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>
</svg>
break;
case IconType.Email:
<svg xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor">
<path stroke-linecap="round"
stroke-linejoin="round"
d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75"/>
</svg>
break;
case IconType.Code:
<svg viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round"
stroke-linejoin="round"
d="M17.25 6.75L22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3l-4.5 16.5"/>
</svg>
break;
case IconType.Hosting:
<svg viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg">
<path d="M3 3h18v18H3V3zm16 16V5H5v14h14zm-8-2h2v-2h2v-2h-2v-2h2V9h-2V7h-2v2H9v2h2v2H9v2h2v2z"/>
</svg>
break;
case IconType.Blazor:
<svg viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg">
<path d="M15.5 2.5c-2.5 0-4.5 2-4.5 4.5v2.5l-2.5 2.5c-1.5 1.5-1.5 4 0 5.5s4 1.5 5.5 0L16.5 15V12.5c0-2.5 2-4.5 4.5-4.5V6c-2.5 0-4.5-2-4.5-4.5h-1zm-3 7.5v2l-1.5 1.5c-.8.8-2.2.8-3 0s-.8-2.2 0-3L9.5 9h3zm3-5c.8 0 1.5.7 1.5 1.5s-.7 1.5-1.5 1.5-1.5-.7-1.5-1.5.7-1.5 1.5-1.5z"/>
</svg>
break;
case IconType.React:
<svg viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg">
<circle cx="12"
cy="12"
r="2"/>
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/>
<ellipse cx="12"
cy="12"
rx="8"
ry="3"
fill="none"
stroke="currentColor"
stroke-width="1"/>
<ellipse cx="12"
cy="12"
rx="3"
ry="8"
fill="none"
stroke="currentColor"
stroke-width="1"/>
</svg>
break;
case IconType.Database:
<svg viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round"
stroke-linejoin="round"
d="M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75"/>
</svg>
break;
case IconType.Docker:
<svg viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg">
<path d="M13 2v7h7c0-3.87-3.13-7-7-7zm-2 0C7.13 2 4 5.13 4 9h7V2zM4 11c0 3.87 3.13 7 7 7v-7H4zm9 7c3.87 0 7-3.13 7-7h-7v7z"/>
</svg>
break;
case IconType.SendArrow:
<svg xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor">
<path stroke-linecap="round"
stroke-linejoin="round"
d="M6 12L3.269 3.126A59.768 59.768 0 0121.485 12 59.77 59.77 0 013.27 20.876L5.999 12zm0 0h7.5"/>
</svg>
break;
case IconType.Menu:
<svg xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
aria-hidden="true">
<path stroke-linecap="round"
stroke-linejoin="round"
d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"/>
</svg>
break;
}
@code {
[Parameter]
public IconType Type { get; set; }
}

View File

@@ -0,0 +1,16 @@
namespace BlazorApp.Components.Icons;
public enum IconType
{
Github,
LinkedIn,
Email,
Code,
Hosting,
Blazor,
React,
Database,
Docker,
SendArrow,
Menu
}

View File

@@ -0,0 +1,19 @@
@* Social media links *@
<div class="social-icons-container">
<a href="https://github.com/bdfin"
class="social-icon-link">
<span class="sr-only">GitHub</span>
<Icon Type="IconType.Github"/>
</a>
<a href="https://www.linkedin.com/in/beau-findlay/"
class="social-icon-link">
<span class="sr-only">LinkedIn</span>
<Icon Type="IconType.LinkedIn"/>
</a>
<a href="mailto:me@beaufindlay.com"
class="social-icon-link">
<span class="sr-only">Email</span>
<Icon Type="IconType.Email"/>
</a>
</div>

View File

@@ -0,0 +1,14 @@
@* Footer component *@
<div class="page-footer">
<footer>
<div class="footer-container">
<div class="footer-content">
<SocialIcons/>
<p class="footer-text">
&copy; @DateTime.Now.Year Beau Findlay. All rights reserved.
</p>
</div>
</div>
</footer>
</div>

View File

@@ -1,17 +1,11 @@
@inherits LayoutComponentBase @inherits LayoutComponentBase
<div class="page"> <div class="px-6 lg:px-10">
<div class="sidebar"> <div class="flex flex-col min-h-screen mx-auto max-w-7xl fade-in">
<NavMenu /> <NavBar/>
</div> <div class="flex-1 py-8">
<main>
<div class="top-row px-4">
<a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
</div>
<article class="content px-4">
@Body @Body
</article> </div>
</main> <Footer/>
</div>
</div> </div>

View File

@@ -0,0 +1,70 @@
@* Navigation bar with mobile menu *@
<div class="navbar">
<header>
<nav aria-label="Global">
<div class="logo-container">
<a href="/"
class="logo-link">
<span class="sr-only">Beau Findlay</span>
<img src="images/logo.webp"
alt="Logo"/>
</a>
</div>
<div class="mobile-menu-button-container">
<label for="mobile-menu-toggle"
class="menu-button">
<span class="sr-only">Open main menu</span>
<Icon Type="IconType.Menu"/>
</label>
</div>
<div class="desktop-nav">
<NavLink href="/experience">Experience</NavLink>
<NavLink href="/about">This app</NavLink>
</div>
</nav>
@* Mobile menu using CSS checkbox hack *@
<input type="checkbox"
id="mobile-menu-toggle"
class="menu-toggle"/>
<div class="mobile-menu-overlay"></div>
<div class="mobile-menu-content">
<div class="mobile-menu-inner">
<div class="mobile-menu-header">
<a href="/"
class="logo-link">
<span class="sr-only">Beau Findlay</span>
<img src="images/logo.webp"
alt="Logo"/>
</a>
<label for="mobile-menu-toggle"
class="close-button">
<span class="sr-only">Close menu</span>
<svg xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
aria-hidden="true">
<path stroke-linecap="round"
stroke-linejoin="round"
d="M6 18L18 6M6 6l12 12"/>
</svg>
</label>
</div>
<div class="mobile-menu-body">
<div class="mobile-nav-links">
<NavLink href="/experience">Experience</NavLink>
<NavLink href="/about">This App</NavLink>
</div>
<div class="mobile-social-divider">
<div class="mobile-social-container">
<SocialIcons/>
</div>
</div>
</div>
</div>
</div>
</header>
</div>

View File

@@ -1,22 +1,31 @@
<div class="top-row ps-3 navbar navbar-dark"> <div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand" href="">BlazorApp</a> <a class="navbar-brand"
href="">BlazorApp</a>
</div> </div>
</div> </div>
<input type="checkbox" title="Navigation menu" class="navbar-toggler" /> <input type="checkbox"
title="Navigation menu"
class="navbar-toggler"/>
<div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()"> <div class="nav-scrollable"
onclick="document.querySelector('.navbar-toggler').click()">
<nav class="nav flex-column"> <nav class="nav flex-column">
<div class="nav-item px-3"> <div class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All"> <NavLink class="nav-link"
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home href=""
Match="NavLinkMatch.All">
<span class="bi bi-house-door-fill-nav-menu"
aria-hidden="true"></span> Home
</NavLink> </NavLink>
</div> </div>
<div class="nav-item px-3"> <div class="nav-item px-3">
<NavLink class="nav-link" href="weather"> <NavLink class="nav-link"
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Weather href="weather">
<span class="bi bi-list-nested-nav-menu"
aria-hidden="true"></span> Weather
</NavLink> </NavLink>
</div> </div>
</nav> </nav>

View File

@@ -0,0 +1,41 @@
@page "/about"
<PageTitle>Beau Findlay - About</PageTitle>
<Title CssClass="text-center pb-4">This App</Title>
<Text>
Below is an overview of how this simple app is made and what
technologies are used. If you'd like to dive straight in, the full
project is available on my
<AnchorLink Href="https://github.com/bdfin/my-portfolio">
GitHub
</AnchorLink>.
</Text>
<section>
<Subtitle>App</Subtitle>
<Text>
This app was originally made using
<AnchorLink Href="https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor">.NET Blazor WASM</AnchorLink>
and then re-written in
<AnchorLink Href="https://react.dev/">React</AnchorLink>
with
<AnchorLink Href="https://www.typescriptlang.org/">TypeScript</AnchorLink>
as a learning exercise. I've now migrated it back to .NET Blazor to take advantage of static server-side
rendering for maximum performance and to remove unnecessary dependencies on large JS and CSS libraries.
</Text>
<Text>
This version uses pure vanilla CSS with CSS variables for theming, eliminating all JavaScript and external
dependencies; the mobile menu uses a CSS checkbox hack for zero-JavaScript interactivity.
</Text>
</section>
<section class="mt-8">
<Subtitle>Hosting & Deployment</Subtitle>
<Text>
TODO: This section
</Text>
</section>

View File

@@ -15,22 +15,28 @@
<h3>Development Mode</h3> <h3>Development Mode</h3>
<p> <p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred. Swapping to <strong>Development</strong> environment will display more detailed information about the error that
occurred.
</p> </p>
<p> <p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong> <strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users. It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong> For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong>
environment variable to <strong>Development</strong>
and restarting the app. and restarting the app.
</p> </p>
@code{ @code{
[CascadingParameter] [CascadingParameter]
private HttpContext? HttpContext { get; set; } private HttpContext? HttpContext { get; set; }
private string? RequestId { get; set; } private string? RequestId { get; set; }
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId); private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
protected override void OnInitialized() => protected override void OnInitialized()
{
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier; RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
}
} }

View File

@@ -0,0 +1,107 @@
@page "/experience"
<PageTitle>Beau Findlay - Experience</PageTitle>
<Title CssClass="text-center">Experience</Title>
<p class="text-center text-xl font-semibold mb-10 ">
Software Engineer since 2018
</p>
<ol class="timeline">
@foreach (var item in experienceTimelineItems)
{
<li class="timeline-item">
<time class="timeline-date">
@item.StartDate - @(item.EndDate ?? "Present")
</time>
<h3 class="timeline-title">
@item.Title @("@") <AnchorLink Href="@item.CompanyUrl">@item.CompanyName</AnchorLink>
</h3>
@foreach (var content in item.Content)
{
<Text>@content</Text>
}
</li>
}
</ol>
<Contact/>
@code {
private readonly List<WorkTimelineItem> experienceTimelineItems =
[
new()
{
StartDate = "September 2021",
Title = "CTO",
CompanyName = "un:hurd music",
CompanyUrl = "https://unhurdmusic.com",
Content =
[
"As one of the founding developers at un:hurd music and now Chief Technology Officer, I built and scaled un:hurd's back-end and cloud infrastructure that serves automated marketing soloutions for tens-of-thousands of artists and musicians.",
"I lead a small but incredibly talented multi-disciplinary team building on the Azure cloud using a .NET backend, React web front-end and a Swift native iOS app."
]
},
new()
{
StartDate = "August 2020",
EndDate = "September 2021",
Title = "Software Development Lead",
CompanyName = "Vouch",
CompanyUrl = "https://vouch.co.uk/",
Content =
[
"At Vouch I lead the backend build of a new version of their tenant referencing software - an AI enhanced chat-bot based system utlising Azure Cognitive Services and various supporting serverless APIs written in .NET Core and hosted on Microsoft Azure."
]
},
new()
{
StartDate = "May 2020",
EndDate = "July 2020",
Title = "Software Developer",
CompanyName = "Paragon ID",
CompanyUrl = "https://www.paragon-id.com/en",
Content =
[
"I joined Paragon ID on a short-term contract where I wrote and deployed two key projects: A complex dashboard for a large construction equipment manufacturer to track assets across various manufacturing stages and a medical assets tracking dashboard deployed and used in multiple hospitals across the UK."
]
},
new()
{
StartDate = "July 2019",
EndDate = "May 2020",
Title = "Software Developer",
CompanyName = "Osborne Technologies",
CompanyUrl = "https://www.osbornetechnologies.co.uk/",
Content =
[
"I joined Osborne Technologies as the only cloud cloud-specialist and lead a project creating the first web-based version of their flag ship visitor management software utilising ASP.NET Core MVC and Microsoft SQL Server on the Microsoft Azure cloud."
]
},
new()
{
StartDate = "September 2018",
EndDate = "September 2019",
Title = " MSc Computing Student",
CompanyName = "Sheffield Hallam University",
CompanyUrl = "https://www.shu.ac.uk/courses/computing/msc-computing/full-time",
Content =
[
"I joined Sheffield Hallam University to study for a Master of Science in Computing. During my time there I completed modules in computer programming and web development, databases and big data, computer hardware, project management and my software development thesis; a .NET web application that compiles astronomy and space exploration data from various APIs into an accessible calendar."
]
}
];
private class WorkTimelineItem
{
public string StartDate { get; init; } = string.Empty;
public string? EndDate { get; init; }
public string Title { get; init; } = string.Empty;
public string CompanyName { get; init; } = string.Empty;
public string CompanyUrl { get; init; } = string.Empty;
public string[] Content { get; init; } = [];
}
}

View File

@@ -1,7 +1,18 @@
@page "/" @page "/"
<PageTitle>Home</PageTitle> <PageTitle>Beau Findlay - Home</PageTitle>
<h1>Hello, world!</h1> <Title CssClass="text-center mb-8">Hi, I'm Beau.</Title>
<Text>
Welcome to your new app. I'm a UK-based software engineer and I love building cool stuff.
</Text>
<Text>
I specialise in C#/.NET development and I've built systems that scale for hundreds-of-thousands of global users.
</Text>
<Text>
I've worked with businesses at all sizes and stages and I'm currently heading up the tech as CTO at a cool startup called
<AnchorLink Href="https://unhurdmusic.com">un:hurd music</AnchorLink>.
</Text>
<Text>
I believe in a privacy-first, information-focussed and performant internet. You won't find any trackers, analytics or the need for a cookie consent policy here.
</Text>

View File

@@ -1,5 +1,13 @@
@page "/not-found" @page "/not-found"
@layout MainLayout
<h3>Not Found</h3> <PageTitle>Beau Findlay - Not Found</PageTitle>
<p>Sorry, the content you are looking for does not exist.</p>
<main class="grid min-h-full place-items-center px-6 py-24 sm:py-32 lg:px-8">
<div class="text-center">
<p class="text-base font-semibold">404</p>
<h1 class="mt-4 text-4xl font-bold tracking-tight">
Page not found
</h1>
<p class="mt-6 text-base leading-7">Sorry, this page doesn't exist.</p>
</div>
</main>

View File

@@ -1,64 +0,0 @@
@page "/weather"
@attribute [StreamRendering]
<PageTitle>Weather</PageTitle>
<h1>Weather</h1>
<p>This component demonstrates showing data.</p>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th aria-label="Temperature in Celsius">Temp. (C)</th>
<th aria-label="Temperature in Fahrenheit">Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
// Simulate asynchronous loading to demonstrate streaming rendering
await Task.Delay(500);
var startDate = DateOnly.FromDateTime(DateTime.Now);
var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
}
private class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public string? Summary { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
}

View File

@@ -1,6 +1,8 @@
<Router AppAssembly="typeof(Program).Assembly" NotFoundPage="typeof(Pages.NotFound)"> @using BlazorApp.Components.Pages
<Router AppAssembly="typeof(Program).Assembly"
NotFoundPage="typeof(NotFound)">
<Found Context="routeData"> <Found Context="routeData">
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" /> <RouteView RouteData="routeData"
<FocusOnNavigate RouteData="routeData" Selector="h1" /> DefaultLayout="typeof(MainLayout)"/>
</Found> </Found>
</Router> </Router>

View File

@@ -0,0 +1,17 @@
@* H2 heading component *@
<h2 class="@CombinedClasses">@ChildContent</h2>
@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
[Parameter]
public string? CssClass { get; set; }
private string CombinedClasses => string.IsNullOrEmpty(CssClass)
? "flex items-center text-2xl py-4 font-semibold"
: $"flex items-center text-2xl py-4 font-semibold {CssClass}";
}

View File

@@ -0,0 +1,17 @@
@* Paragraph component *@
<p class="@CombinedClasses">@ChildContent</p>
@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
[Parameter]
public string? CssClass { get; set; }
private string CombinedClasses => string.IsNullOrEmpty(CssClass)
? "text-paragraph"
: $"text-paragraph {CssClass}";
}

View File

@@ -0,0 +1,17 @@
@* H1 heading component *@
<h1 class="@CombinedClasses">@ChildContent</h1>
@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
[Parameter]
public string? CssClass { get; set; }
private string CombinedClasses => string.IsNullOrEmpty(CssClass)
? "text-4xl py-4"
: $"text-4xl py-4 {CssClass}";
}

View File

@@ -9,3 +9,9 @@
@using BlazorApp @using BlazorApp
@using BlazorApp.Components @using BlazorApp.Components
@using BlazorApp.Components.Layout @using BlazorApp.Components.Layout
@using BlazorApp.Components.Icons
@using BlazorApp.Components.AnchorLink
@using BlazorApp.Components.Contact
@using BlazorApp.Components.Typography.Title
@using BlazorApp.Components.Typography.Subtitle
@using BlazorApp.Components.Typography.Text

View File

@@ -10,10 +10,11 @@ var app = builder.Build();
// Configure the HTTP request pipeline. // Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment()) if (!app.Environment.IsDevelopment())
{ {
app.UseExceptionHandler("/Error", createScopeForErrors: true); app.UseExceptionHandler("/Error", true);
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts(); app.UseHsts();
} }
app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true); app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true);
app.UseHttpsRedirection(); app.UseHttpsRedirection();

View File

@@ -1,23 +1,23 @@
{ {
"$schema": "https://json.schemastore.org/launchsettings.json", "$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": { "profiles": {
"http": { "http": {
"commandName": "Project", "commandName": "Project",
"dotnetRunMessages": true, "dotnetRunMessages": true,
"launchBrowser": true, "launchBrowser": true,
"applicationUrl": "http://localhost:5064", "applicationUrl": "http://localhost:5064",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
} }
}, },
"https": { "https": {
"commandName": "Project", "commandName": "Project",
"dotnetRunMessages": true, "dotnetRunMessages": true,
"launchBrowser": true, "launchBrowser": true,
"applicationUrl": "https://localhost:7162;http://localhost:5064", "applicationUrl": "https://localhost:7162;http://localhost:5064",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
}
} }
} }
} }
}

View File

@@ -2,109 +2,109 @@
/* Fade In */ /* Fade In */
.fade-in { .fade-in {
animation: fadeInAnimation ease 1s; animation: fadeInAnimation ease 1s;
animation-iteration-count: 1; animation-iteration-count: 1;
animation-fill-mode: forwards; animation-fill-mode: forwards;
} }
@keyframes fadeInAnimation { @keyframes fadeInAnimation {
from { from {
opacity: 0; opacity: 0;
} }
to { to {
opacity: 1; opacity: 1;
} }
} }
/* Slide */ /* Slide */
@keyframes slideInRight { @keyframes slideInRight {
from { from {
transform: translateX(100%); transform: translateX(100%);
} }
to { to {
transform: translateX(0); transform: translateX(0);
} }
} }
@keyframes slideOutRight { @keyframes slideOutRight {
from { from {
transform: translateX(0); transform: translateX(0);
} }
to { to {
transform: translateX(100%); transform: translateX(100%);
} }
} }
@keyframes fadeInUp { @keyframes fadeInUp {
from { from {
opacity: 0; opacity: 0;
transform: translateY(20px); transform: translateY(20px);
} }
to { to {
opacity: 1; opacity: 1;
transform: translateY(0); transform: translateY(0);
} }
} }
.fade-in-up { .fade-in-up {
animation: fadeInUp var(--transition-slow) var(--transition-timing); animation: fadeInUp var(--transition-slow) var(--transition-timing);
} }
/* Spinner */ /* Spinner */
.spinner { .spinner {
display: inline-block; display: inline-block;
width: 24px; width: 24px;
height: 24px; height: 24px;
border: 3px solid var(--color-slate-700); border: 3px solid var(--color-slate-700);
border-top-color: var(--color-slate-50); border-top-color: var(--color-slate-50);
border-radius: 50%; border-radius: 50%;
animation: spin 0.8s linear infinite; animation: spin 0.8s linear infinite;
} }
@keyframes spin { @keyframes spin {
to { to {
transform: rotate(360deg); transform: rotate(360deg);
} }
} }
/* Pulse */ /* Pulse */
.pulse { .pulse {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
} }
@keyframes pulse { @keyframes pulse {
0%, 100% { 0%, 100% {
opacity: 1; opacity: 1;
} }
50% { 50% {
opacity: 0.5; opacity: 0.5;
} }
} }
/* Transitions */ /* Transitions */
.transition { .transition {
transition: all var(--transition-base) var(--transition-timing); transition: all var(--transition-base) var(--transition-timing);
} }
.transition-colors { .transition-colors {
transition: color var(--transition-base) var(--transition-timing), transition: color var(--transition-base) var(--transition-timing),
background-color var(--transition-base) var(--transition-timing), background-color var(--transition-base) var(--transition-timing),
border-color var(--transition-base) var(--transition-timing); border-color var(--transition-base) var(--transition-timing);
} }
.transition-transform { .transition-transform {
transition: transform var(--transition-base) var(--transition-timing); transition: transform var(--transition-base) var(--transition-timing);
} }
.transition-opacity { .transition-opacity {
transition: opacity var(--transition-base) var(--transition-timing); transition: opacity var(--transition-base) var(--transition-timing);
} }
/* Hover */ /* Hover */
.hover-scale:hover { .hover-scale:hover {
transform: scale(1.05); transform: scale(1.05);
} }
.hover-lift:hover { .hover-lift:hover {
transform: translateY(-2px); transform: translateY(-2px);
} }

View File

@@ -3,6 +3,7 @@
@import url('variables.css'); @import url('variables.css');
@import url('reset.css'); @import url('reset.css');
@import url('base.css'); @import url('base.css');
@import url('utilities.css');
@import url('layout.css'); @import url('layout.css');
@import url('components.css'); @import url('components.css');
@import url('animations.css'); @import url('animations.css');

View File

@@ -1,11 +1,13 @@
/* Base Styles */ /* Base Styles */
body { body {
background-color: var(--color-black); background-color: var(--color-black);
color: var(--color-slate-50); color: var(--color-slate-50);
font-family: var(--font-mono); font-family: var(--font-mono);
font-size: var(--font-size-base); font-size: var(--font-size-base);
line-height: var(--line-height-normal); line-height: var(--line-height-normal);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
} }
/* Typography */ /* Typography */
@@ -15,76 +17,78 @@ h3,
h4, h4,
h5, h5,
h6 { h6 {
font-weight: var(--font-weight-semibold); font-weight: var(--font-weight-semibold);
line-height: var(--line-height-tight); line-height: var(--line-height-tight);
} }
h1 { h1 {
font-size: var(--font-size-3xl); font-size: var(--font-size-3xl);
} }
h2 { h2 {
font-size: var(--font-size-2xl); font-size: var(--font-size-2xl);
} }
h3 { h3 {
font-size: var(--font-size-xl); font-size: var(--font-size-xl);
} }
p { p {
margin-bottom: var(--space-4); margin-bottom: var(--space-4);
} }
/* Links */ /* Links */
a { a {
color: var(--color-slate-50); color: var(--color-slate-50);
transition: color var(--transition-base) var(--transition-timing); display: inline-block;
transition: transform var(--transition-base) var(--transition-timing);
} }
a:hover { a:hover {
color: var(--color-slate-300); transform: translateY(2px);
color: var(--color-slate-50);
} }
a:focus-visible { a:focus-visible {
outline: 2px solid var(--color-slate-50); outline: 2px solid var(--color-slate-50);
outline-offset: 2px; outline-offset: 2px;
} }
/* Focus */ /* Focus */
*:focus-visible { *:focus-visible {
outline: 2px solid var(--color-slate-50); outline: 2px solid var(--color-slate-50);
outline-offset: 2px; outline-offset: 2px;
} }
button:focus-visible, button:focus-visible,
input:focus-visible, input:focus-visible,
textarea:focus-visible, textarea:focus-visible,
select:focus-visible { select:focus-visible {
outline: 2px solid var(--color-slate-50); outline: 2px solid var(--color-slate-50);
outline-offset: 2px; outline-offset: 2px;
} }
/* Selection */ /* Selection */
::selection { ::selection {
background-color: var(--color-slate-50); background-color: var(--color-slate-50);
color: var(--color-black); color: var(--color-black);
} }
/* Scrollbar */ /* Scrollbar */
body::-webkit-scrollbar { body::-webkit-scrollbar {
width: 10px; width: 10px;
} }
body::-webkit-scrollbar-track { body::-webkit-scrollbar-track {
background: var(--color-white); background: var(--color-white);
} }
body::-webkit-scrollbar-thumb { body::-webkit-scrollbar-thumb {
background-color: var(--color-black); background-color: var(--color-black);
border: 1px solid var(--color-white); border: 1px solid var(--color-white);
} }
body { body {
scrollbar-width: thin; scrollbar-width: thin;
scrollbar-color: var(--color-black) var(--color-white); scrollbar-color: var(--color-black) var(--color-white);
} }

View File

@@ -2,213 +2,598 @@
/* Buttons */ /* Buttons */
.btn { .btn {
display: inline-block; display: inline-block;
padding: var(--space-3) var(--space-6); padding: var(--space-3) var(--space-6);
font-weight: var(--font-weight-medium); font-weight: var(--font-weight-medium);
text-align: center; text-align: center;
border-radius: var(--radius-md); border-radius: var(--radius-md);
transition: all var(--transition-base) var(--transition-timing); transition: all var(--transition-base) var(--transition-timing);
cursor: pointer; cursor: pointer;
} }
.btn:focus-visible { .btn:focus-visible {
outline: 2px solid var(--color-slate-50); outline: 2px solid var(--color-slate-50);
outline-offset: 2px; outline-offset: 2px;
} }
.btn-primary { .btn-primary {
background-color: var(--color-slate-50); background-color: var(--color-slate-50);
color: var(--color-black); color: var(--color-black);
} }
.btn-primary:hover { .btn-primary:hover {
background-color: var(--color-slate-200); background-color: var(--color-slate-200);
} }
.btn-secondary { .btn-secondary {
background-color: transparent; background-color: transparent;
color: var(--color-slate-50); color: var(--color-slate-50);
border: 1px solid var(--color-slate-50); border: 1px solid var(--color-slate-50);
} }
.btn-secondary:hover { .btn-secondary:hover {
background-color: var(--color-slate-50); background-color: var(--color-slate-50);
color: var(--color-black); color: var(--color-black);
} }
.btn:disabled { .btn:disabled {
opacity: 0.5; opacity: 0.5;
cursor: not-allowed; cursor: not-allowed;
} }
/* Inputs */ /* Inputs */
.input { .input {
width: 100%; width: 100%;
padding: var(--space-3); padding: var(--space-3);
background-color: var(--color-black); background-color: var(--color-black);
border: 1px solid var(--color-slate-700); border: 1px solid var(--color-slate-700);
border-radius: var(--radius-md); border-radius: var(--radius-md);
color: var(--color-slate-50); color: var(--color-slate-50);
font-size: var(--font-size-base); font-size: var(--font-size-base);
} }
.input:focus { .input:focus {
outline: none; outline: none;
border-color: var(--color-slate-50); border-color: var(--color-slate-50);
} }
.input:invalid { .input:invalid {
border-color: #ef4444; border-color: #ef4444;
} }
.input::placeholder { .input::placeholder {
color: var(--color-slate-500); color: var(--color-slate-500);
} }
/* Textareas */ /* Textareas */
.textarea { .textarea {
width: 100%; width: 100%;
padding: var(--space-3); padding: var(--space-3);
background-color: var(--color-black); background-color: var(--color-black);
border: 1px solid var(--color-slate-700); border: 1px solid var(--color-slate-700);
border-radius: var(--radius-md); border-radius: var(--radius-md);
color: var(--color-slate-50); color: var(--color-slate-50);
font-size: var(--font-size-base); font-size: var(--font-size-base);
resize: vertical; resize: vertical;
min-height: 120px; min-height: 120px;
} }
.textarea:focus { .textarea:focus {
outline: none; outline: none;
border-color: var(--color-slate-50); border-color: var(--color-slate-50);
} }
/* Labels */ /* Labels */
.label { .label {
display: block; display: block;
margin-bottom: var(--space-2); margin-bottom: var(--space-2);
font-size: var(--font-size-sm); font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium); font-weight: var(--font-weight-medium);
} }
/* Cards */ /* Cards */
.card { .card {
background-color: var(--color-slate-900); background-color: var(--color-slate-900);
border-radius: var(--radius-lg); border-radius: var(--radius-lg);
padding: var(--space-6); padding: var(--space-6);
border: 1px solid var(--color-slate-800); border: 1px solid var(--color-slate-800);
} }
/* Navigation */ /* NavBar */
.nav { .navbar header {
display: flex; padding-top: var(--space-6);
align-items: center;
justify-content: space-between;
padding: var(--space-6) 0;
} }
.nav-link { .navbar nav {
padding: var(--space-2) var(--space-4); margin-left: auto;
color: var(--color-slate-50); margin-right: auto;
font-size: var(--font-size-sm); display: flex;
transition: color var(--transition-base) var(--transition-timing); max-width: 80rem;
align-items: center;
justify-content: space-between;
} }
.nav-link:hover { .navbar .logo-container {
color: var(--color-slate-300); display: flex;
} }
.nav-link-active { .navbar .logo-link {
font-weight: var(--font-weight-semibold); margin: -0.375rem;
padding: 0.375rem;
} }
/* Footer */ .navbar .logo-link img {
.footer { height: 4rem;
padding: var(--space-8) 0; width: auto;
margin-top: auto; }
border-top: 1px solid var(--color-slate-800);
text-align: center; .navbar .mobile-menu-button-container {
font-size: var(--font-size-sm); display: flex;
color: var(--color-slate-400); }
.navbar .menu-button {
margin: -0.625rem;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: var(--radius-md);
padding: 0.625rem;
cursor: pointer;
}
.navbar .menu-button svg {
height: 1.5rem;
width: 1.5rem;
}
.navbar .desktop-nav {
display: none;
}
.navbar .desktop-nav a {
font-size: var(--font-size-sm);
font-weight: var(--font-weight-semibold);
line-height: 1.5;
} }
/* Mobile Menu (CSS-only) */ /* Mobile Menu (CSS-only) */
.mobile-menu-toggle { .menu-toggle {
display: none; display: none;
}
.mobile-menu-button {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
cursor: pointer;
font-size: var(--font-size-2xl);
} }
.mobile-menu-overlay { .mobile-menu-overlay {
display: none; display: none;
position: fixed; position: fixed;
inset: 0; inset: 0;
background-color: rgba(0, 0, 0, 0.5); background-color: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px); backdrop-filter: blur(4px);
z-index: var(--z-overlay); z-index: var(--z-overlay);
} }
.mobile-menu { .menu-toggle:checked ~ .mobile-menu-overlay {
display: none; display: block;
position: fixed;
top: 0;
right: 0;
bottom: 0;
width: 80%;
max-width: 300px;
background-color: var(--color-black);
border-left: 1px solid var(--color-slate-800);
padding: var(--space-6);
z-index: var(--z-modal);
transform: translateX(100%);
transition: transform var(--transition-slow) var(--transition-timing);
} }
.mobile-menu-toggle:checked ~ .mobile-menu-overlay { .mobile-menu-content {
display: block; display: none;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: var(--z-modal);
background-color: var(--color-black);
width: 100%;
padding: var(--space-6);
color: white;
transform: translateX(100%);
transition: transform var(--transition-slow) var(--transition-timing);
} }
.mobile-menu-toggle:checked ~ .mobile-menu { .menu-toggle:checked ~ .mobile-menu-content {
display: block; display: block;
transform: translateX(0); transform: translateX(0);
} }
.mobile-menu-close { .mobile-menu-inner {
display: flex; display: flex;
justify-content: flex-end; flex-direction: column;
margin-bottom: var(--space-6); height: 100%;
font-size: var(--font-size-2xl); }
cursor: pointer;
.mobile-menu-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.close-button {
margin: -0.625rem;
border-radius: var(--radius-md);
padding: 0.625rem;
cursor: pointer;
}
.close-button svg {
height: 1.5rem;
width: 1.5rem;
}
.mobile-menu-body {
margin-top: var(--space-6);
flex: 1 1 0%;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.mobile-nav-links {
margin-top: var(--space-2);
}
.mobile-nav-links a {
margin-left: -0.75rem;
margin-right: -0.75rem;
display: block;
border-radius: var(--radius-lg);
padding: 0.75rem;
padding-top: var(--space-2);
padding-bottom: var(--space-2);
font-size: var(--font-size-base);
font-weight: var(--font-weight-semibold);
line-height: 1.75;
}
.mobile-social-divider {
border-top: 1px solid rgba(226, 232, 240, 0.1);
padding-top: var(--space-8);
}
.mobile-social-container {
display: flex;
justify-content: center;
align-items: center;
}
/* Prevent body scroll when mobile menu is open */
.menu-toggle:checked ~ * {
overflow: hidden;
}
body:has(.menu-toggle:checked) {
overflow: hidden;
}
@media (min-width: 640px) {
.mobile-menu-content {
max-width: 24rem;
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.1);
border-left: 2px solid var(--color-slate-800);
}
}
@media (min-width: 1024px) {
.navbar .logo-container {
flex: 1 1 0%;
}
.navbar .mobile-menu-button-container {
display: none;
}
.navbar .desktop-nav {
display: flex;
column-gap: var(--space-12);
}
.mobile-menu-content {
display: none;
}
}
/* Footer */
.page-footer footer {
margin-top: auto;
}
.page-footer .footer-container {
margin-left: auto;
margin-right: auto;
padding-top: var(--space-8);
padding-bottom: var(--space-8);
}
.page-footer .footer-content {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--space-4);
}
.page-footer .footer-text {
font-size: var(--font-size-xs);
line-height: 1.25;
color: var(--color-slate-50);
margin-bottom: 0;
}
@media (min-width: 768px) {
.page-footer .footer-content {
flex-direction: row;
align-items: center;
justify-content: space-between;
gap: 0;
}
.page-footer .footer-text {
order: 1;
}
}
/* Social Icons */
.social-icons-container {
display: flex;
gap: var(--space-6);
}
.social-icon-link {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 40px;
min-height: 40px;
color: var(--color-slate-50);
transition: transform var(--transition-base) var(--transition-timing);
}
.social-icon-link svg {
width: 24px;
height: 24px;
}
.social-icon-link:hover {
transform: translateY(2px);
color: var(--color-slate-50);
}
@media (min-width: 768px) {
.social-icons-container {
order: 2;
}
.social-icon-link {
min-width: auto;
min-height: auto;
}
.social-icon-link svg {
width: 20px;
height: 20px;
}
}
/* Tech Icons */
.tech-icons-wrapper {
margin-left: auto;
margin-right: auto;
max-width: 64rem;
}
.tech-icons-title {
font-size: var(--font-size-xl);
text-align: center;
margin-bottom: var(--space-10);
font-weight: var(--font-weight-semibold);
}
.tech-icons-grid {
display: flex;
flex-direction: column;
gap: var(--space-6);
text-align: center;
margin-left: auto;
margin-right: auto;
margin-top: var(--space-4);
}
.tech-icon-item {
display: flex;
flex-direction: column;
align-items: center;
}
.tech-icon-item svg {
width: 34px;
height: 34px;
}
.tech-icon-item p {
margin-top: var(--space-2);
font-size: var(--font-size-sm);
}
@media (min-width: 768px) {
.tech-icons-grid {
flex-direction: row;
justify-content: space-evenly;
gap: 0;
}
}
/* AboutTabs */
.tabs-subtitle {
font-weight: var(--font-weight-bold);
font-size: var(--font-size-lg);
margin-top: var(--space-4);
margin-bottom: var(--space-4);
}
/* Contact Section */
.contact-section {
margin-bottom: var(--space-10);
margin-top: var(--space-12);
text-align: center;
}
.contact-button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--space-2);
border: 1px solid var(--color-slate-700);
background-color: var(--color-black);
padding: 0.625rem 0.875rem;
margin-top: var(--space-2);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-semibold);
color: var(--color-slate-50);
transition: transform var(--transition-base) var(--transition-timing);
}
.contact-button:hover {
transform: translateY(2px);
color: var(--color-slate-50);
}
.contact-button:focus-visible {
outline: 2px solid var(--color-slate-50);
outline-offset: 2px;
}
.contact-button svg {
width: 16px;
height: 16px;
flex-shrink: 0;
}
/* Text Paragraph */
.text-paragraph {
font-size: var(--font-size-lg);
padding-top: var(--space-4);
padding-bottom: var(--space-4);
}
@media (min-width: 768px) {
.text-paragraph {
padding-top: var(--space-2);
padding-bottom: var(--space-2);
}
} }
/* Links */ /* Links */
.link { .link {
color: var(--color-slate-50); color: var(--color-slate-50);
text-decoration: underline; text-decoration: underline;
text-underline-offset: 2px; text-underline-offset: 2px;
} }
.link:hover { .link:hover {
color: var(--color-slate-300); color: var(--color-slate-300);
} }
/* Icon Buttons */ /* Icon Buttons */
.icon-button { .icon-button {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: var(--space-2); padding: var(--space-2);
transition: opacity var(--transition-base) var(--transition-timing); transition: opacity var(--transition-base) var(--transition-timing);
} }
.icon-button:hover { .icon-button:hover {
opacity: 0.7; opacity: 0.7;
}
/* Timeline */
.timeline {
position: relative;
border-left: 1px solid var(--color-slate-600);
list-style: none;
padding-left: 0;
}
.timeline-item {
margin-bottom: var(--space-10);
margin-left: var(--space-4);
position: relative;
}
.timeline-marker {
position: absolute;
width: 0.75rem;
height: 0.75rem;
background-color: var(--color-slate-600);
border-radius: 50%;
left: -1.875rem;
top: 0.375rem;
border: 2px solid var(--color-black);
}
.timeline-date {
display: block;
margin-bottom: var(--space-1);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-normal);
line-height: 1;
color: var(--color-slate-400);
}
.timeline-title {
font-size: var(--font-size-2xl);
font-weight: var(--font-weight-semibold);
color: var(--color-slate-50);
margin-bottom: var(--space-2);
}
/* Tabs (CSS Radio Buttons) */
.tabs-container {
margin-top: var(--space-4);
}
.tab-radio {
display: none;
}
.tab-labels {
display: flex;
gap: var(--space-8);
border-bottom: 1px solid var(--color-slate-700);
margin-bottom: var(--space-10);
overflow-x: auto;
}
.tab-label {
padding: var(--space-6) 0;
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
color: var(--color-slate-400);
border-bottom: 4px solid transparent;
margin-bottom: -1px;
cursor: pointer;
white-space: nowrap;
transition: all var(--transition-base) var(--transition-timing);
}
.tab-label:hover {
color: var(--color-slate-200);
border-bottom-color: var(--color-slate-700);
}
#tab0:checked ~ .tab-labels label[for="tab0"],
#tab1:checked ~ .tab-labels label[for="tab1"],
#tab2:checked ~ .tab-labels label[for="tab2"],
#tab3:checked ~ .tab-labels label[for="tab3"] {
color: var(--color-slate-200);
border-bottom-color: var(--color-slate-300);
}
.tab-panel {
display: none;
}
#tab0:checked ~ .tab-panels #panel0,
#tab1:checked ~ .tab-panels #panel1,
#tab2:checked ~ .tab-panels #panel2,
#tab3:checked ~ .tab-panels #panel3 {
display: block;
} }

View File

@@ -2,239 +2,17 @@
/* Container */ /* Container */
.container { .container {
width: 100%; width: 100%;
max-width: var(--container-max-width); max-width: var(--container-max-width);
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
padding-left: var(--space-6); padding-left: var(--space-6);
padding-right: var(--space-6); padding-right: var(--space-6);
} }
@media (min-width: 1024px) { @media (min-width: 1024px) {
.container { .container {
padding-left: var(--space-10); padding-left: var(--space-10);
padding-right: var(--space-10); padding-right: var(--space-10);
} }
}
/* Flexbox */
.flex {
display: flex;
}
.flex-col {
flex-direction: column;
}
.flex-row {
flex-direction: row;
}
.flex-1 {
flex: 1 1 0%;
}
.items-start {
align-items: flex-start;
}
.items-center {
align-items: center;
}
.items-end {
align-items: flex-end;
}
.justify-start {
justify-content: flex-start;
}
.justify-center {
justify-content: center;
}
.justify-end {
justify-content: flex-end;
}
.justify-between {
justify-content: space-between;
}
.gap-1 {
gap: var(--space-1);
}
.gap-2 {
gap: var(--space-2);
}
.gap-3 {
gap: var(--space-3);
}
.gap-4 {
gap: var(--space-4);
}
.gap-6 {
gap: var(--space-6);
}
.gap-8 {
gap: var(--space-8);
}
/* Grid */
.grid {
display: grid;
}
.grid-cols-1 {
grid-template-columns: repeat(1, minmax(0, 1fr));
}
.grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.grid-cols-3 {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.grid-cols-4 {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
@media (min-width: 640px) {
.sm\:grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.sm\:grid-cols-3 {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
@media (min-width: 768px) {
.md\:grid-cols-3 {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.md\:grid-cols-4 {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
}
@media (min-width: 1024px) {
.lg\:grid-cols-4 {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.lg\:grid-cols-5 {
grid-template-columns: repeat(5, minmax(0, 1fr));
}
}
/* Spacing */
.min-h-screen {
min-height: 100vh;
}
.p-2 { padding: var(--space-2); }
.p-4 { padding: var(--space-4); }
.p-6 { padding: var(--space-6); }
.p-8 { padding: var(--space-8); }
.px-2 { padding-left: var(--space-2); padding-right: var(--space-2); }
.px-4 { padding-left: var(--space-4); padding-right: var(--space-4); }
.px-6 { padding-left: var(--space-6); padding-right: var(--space-6); }
.px-8 { padding-left: var(--space-8); padding-right: var(--space-8); }
.py-2 { padding-top: var(--space-2); padding-bottom: var(--space-2); }
.py-4 { padding-top: var(--space-4); padding-bottom: var(--space-4); }
.py-6 { padding-top: var(--space-6); padding-bottom: var(--space-6); }
.py-8 { padding-top: var(--space-8); padding-bottom: var(--space-8); }
.m-2 { margin: var(--space-2); }
.m-4 { margin: var(--space-4); }
.m-6 { margin: var(--space-6); }
.m-8 { margin: var(--space-8); }
.mx-auto { margin-left: auto; margin-right: auto; }
.mt-2 { margin-top: var(--space-2); }
.mt-4 { margin-top: var(--space-4); }
.mt-6 { margin-top: var(--space-6); }
.mt-8 { margin-top: var(--space-8); }
.mb-2 { margin-bottom: var(--space-2); }
.mb-4 { margin-bottom: var(--space-4); }
.mb-6 { margin-bottom: var(--space-6); }
.mb-8 { margin-bottom: var(--space-8); }
/* Display */
.hidden {
display: none;
}
.block {
display: block;
}
.inline-block {
display: inline-block;
}
/* Responsive */
@media (min-width: 1024px) {
.lg\:flex {
display: flex;
}
.lg\:hidden {
display: none;
}
.lg\:block {
display: block;
}
}
/* Widths */
.w-full {
width: 100%;
}
.max-w-sm {
max-width: 24rem;
}
.max-w-md {
max-width: 28rem;
}
.max-w-lg {
max-width: 32rem;
}
.max-w-xl {
max-width: 36rem;
}
.max-w-2xl {
max-width: 42rem;
}
.max-w-7xl {
max-width: 80rem;
}
/* Text */
.text-center {
text-align: center;
}
.text-left {
text-align: left;
}
.text-right {
text-align: right;
} }

View File

@@ -3,30 +3,30 @@
*, *,
*::before, *::before,
*::after { *::after {
box-sizing: border-box; box-sizing: border-box;
} }
* { * {
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
html { html {
-webkit-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;
-moz-tab-size: 4; -moz-tab-size: 4;
tab-size: 4; tab-size: 4;
} }
html:focus-within { html:focus-within {
scroll-behavior: smooth; scroll-behavior: smooth;
} }
body { body {
min-height: 100vh; min-height: 100vh;
line-height: 1.5; line-height: 1.5;
text-rendering: optimizeSpeed; text-rendering: optimizeSpeed;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
img, img,
@@ -34,44 +34,44 @@ picture,
video, video,
canvas, canvas,
svg { svg {
display: block; display: block;
max-width: 100%; max-width: 100%;
} }
input, input,
button, button,
textarea, textarea,
select { select {
font: inherit; font: inherit;
} }
button { button {
background: none; background: none;
border: none; border: none;
cursor: pointer; cursor: pointer;
} }
ul, ul,
ol { ol {
list-style: none; list-style: none;
} }
a { a {
text-decoration: none; text-decoration: none;
color: inherit; color: inherit;
} }
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
html:focus-within { html:focus-within {
scroll-behavior: auto; scroll-behavior: auto;
} }
*, *,
*::before, *::before,
*::after { *::after {
animation-duration: 0.01ms !important; animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important; animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important; transition-duration: 0.01ms !important;
scroll-behavior: auto !important; scroll-behavior: auto !important;
} }
} }

View File

@@ -0,0 +1,641 @@
/* Utility Classes */
/* Display */
.hidden {
display: none;
}
.block {
display: block;
}
.inline-block {
display: inline-block;
}
.inline-flex {
display: inline-flex;
}
.flow-root {
display: flow-root;
}
/* Flexbox */
.flex {
display: flex;
}
.flex-col {
flex-direction: column;
}
.flex-row {
flex-direction: row;
}
.flex-1 {
flex: 1 1 0;
}
.items-start {
align-items: flex-start;
}
.items-center {
align-items: center;
}
.items-end {
align-items: flex-end;
}
.justify-start {
justify-content: flex-start;
}
.justify-center {
justify-content: center;
}
.justify-end {
justify-content: flex-end;
}
.justify-between {
justify-content: space-between;
}
/* Gap */
.gap-1 {
gap: var(--space-1);
}
.gap-2 {
gap: var(--space-2);
}
.gap-3 {
gap: var(--space-3);
}
.gap-4 {
gap: var(--space-4);
}
.gap-6 {
gap: var(--space-6);
}
.gap-8 {
gap: var(--space-8);
}
/* Grid */
.grid {
display: grid;
}
.grid-cols-1 {
grid-template-columns: repeat(1, minmax(0, 1fr));
}
.grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.grid-cols-3 {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.grid-cols-4 {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.place-items-center {
place-items: center;
}
/* Spacing */
.min-h-screen {
min-height: 100vh;
}
.min-h-full {
min-height: 100%;
}
.h-full {
height: 100%;
}
.h-6 {
height: 1.5rem;
}
.h-16 {
height: 4rem;
}
.w-6 {
width: 1.5rem;
}
.w-auto {
width: auto;
}
.w-full {
width: 100%;
}
/* Max Widths */
.max-w-sm {
max-width: 24rem;
}
.max-w-md {
max-width: 28rem;
}
.max-w-lg {
max-width: 32rem;
}
.max-w-xl {
max-width: 36rem;
}
.max-w-2xl {
max-width: 42rem;
}
.max-w-4xl {
max-width: 64rem;
}
.max-w-7xl {
max-width: 80rem;
}
/* Padding */
.p-2 {
padding: var(--space-2);
}
.p-4 {
padding: var(--space-4);
}
.p-6 {
padding: var(--space-6);
}
.p-8 {
padding: var(--space-8);
}
.p-1\.5 {
padding: 0.375rem;
}
.p-2\.5 {
padding: 0.625rem;
}
.px-2 {
padding-left: var(--space-2);
padding-right: var(--space-2);
}
.px-3 {
padding-left: 0.75rem;
padding-right: 0.75rem;
}
.px-4 {
padding-left: var(--space-4);
padding-right: var(--space-4);
}
.px-6 {
padding-left: var(--space-6);
padding-right: var(--space-6);
}
.px-8 {
padding-left: var(--space-8);
padding-right: var(--space-8);
}
.py-2 {
padding-top: var(--space-2);
padding-bottom: var(--space-2);
}
.py-4 {
padding-top: var(--space-4);
padding-bottom: var(--space-4);
}
.py-6 {
padding-top: var(--space-6);
padding-bottom: var(--space-6);
}
.py-8 {
padding-top: var(--space-8);
padding-bottom: var(--space-8);
}
.py-24 {
padding-top: 6rem;
padding-bottom: 6rem;
}
.pt-6 {
padding-top: var(--space-6);
}
.pt-8 {
padding-top: var(--space-8);
}
.pb-4 {
padding-bottom: var(--space-4);
}
/* Margins */
.m-2 {
margin: var(--space-2);
}
.m-4 {
margin: var(--space-4);
}
.m-6 {
margin: var(--space-6);
}
.m-8 {
margin: var(--space-8);
}
.-m-1\.5 {
margin: -0.375rem;
}
.-m-2\.5 {
margin: -0.625rem;
}
.mx-auto {
margin-left: auto;
margin-right: auto;
}
.-mx-3 {
margin-left: -0.75rem;
margin-right: -0.75rem;
}
.-my-6 {
margin-top: -1.5rem;
margin-bottom: -1.5rem;
}
.mt-2 {
margin-top: var(--space-2);
}
.mt-4 {
margin-top: var(--space-4);
}
.mt-6 {
margin-top: var(--space-6);
}
.mt-8 {
margin-top: var(--space-8);
}
.mt-12 {
margin-top: var(--space-12);
}
.mt-auto {
margin-top: auto;
}
.mb-2 {
margin-bottom: var(--space-2);
}
.mb-4 {
margin-bottom: var(--space-4);
}
.mb-6 {
margin-bottom: var(--space-6);
}
.mb-8 {
margin-bottom: var(--space-8);
}
.mb-10 {
margin-bottom: var(--space-10);
}
.mb-12 {
margin-bottom: var(--space-12);
}
/* Space Between */
.space-x-6 > * + * {
margin-left: var(--space-6);
}
.space-y-2 > * + * {
margin-top: var(--space-2);
}
.space-y-10 > * + * {
margin-top: var(--space-10);
}
/* Typography */
.text-center {
text-align: center;
}
.text-left {
text-align: left;
}
.text-right {
text-align: right;
}
.text-xs {
font-size: var(--font-size-xs);
}
.text-sm {
font-size: var(--font-size-sm);
}
.text-base {
font-size: var(--font-size-base);
}
.text-lg {
font-size: var(--font-size-lg);
}
.text-xl {
font-size: var(--font-size-xl);
}
.text-2xl {
font-size: var(--font-size-2xl);
}
.text-3xl {
font-size: var(--font-size-3xl);
}
.text-4xl {
font-size: var(--font-size-4xl);
}
.font-normal {
font-weight: var(--font-weight-normal);
}
.font-medium {
font-weight: var(--font-weight-medium);
}
.font-semibold {
font-weight: var(--font-weight-semibold);
}
.font-bold {
font-weight: var(--font-weight-bold);
}
.leading-tight {
line-height: var(--line-height-tight);
}
.leading-normal {
line-height: var(--line-height-normal);
}
.leading-relaxed {
line-height: var(--line-height-relaxed);
}
.leading-6 {
line-height: 1.5;
}
.leading-7 {
line-height: 1.75;
}
.tracking-tight {
letter-spacing: -0.025em;
}
.underline {
text-decoration: underline;
}
.underline-offset-2 {
text-underline-offset: 2px;
}
.underline-offset-4 {
text-underline-offset: 4px;
}
/* Positioning */
.fixed {
position: fixed;
}
.inset-0 {
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.inset-y-0 {
top: 0;
bottom: 0;
}
/* Border */
.border-t {
border-top: 1px solid rgba(226, 232, 240, 0.1);
}
.divide-y > * + * {
border-top: 1px solid rgba(226, 232, 240, 0.1);
}
.rounded-lg {
border-radius: var(--radius-lg);
}
.rounded-md {
border-radius: var(--radius-md);
}
/* Z-index */
.z-10 {
z-index: var(--z-dropdown);
}
/* Overflow */
.overflow-y-auto {
overflow-y: auto;
}
/* Cursor */
.cursor-pointer {
cursor: pointer;
}
/* Screen Reader Only */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
/* Responsive Utilities */
@media (min-width: 640px) {
.sm\:max-w-sm {
max-width: 24rem;
}
.sm\:ring-1 {
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.1);
}
.sm\:border-l-2 {
border-left: 2px solid var(--color-slate-800);
}
.sm\:py-32 {
padding-top: 8rem;
padding-bottom: 8rem;
}
.sm\:grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.sm\:grid-cols-3 {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
@media (min-width: 768px) {
.md\:flex {
display: flex;
}
.md\:flex-row {
flex-direction: row;
}
.md\:justify-evenly {
justify-content: space-evenly;
}
.md\:items-center {
align-items: center;
}
.md\:justify-between {
justify-content: space-between;
}
.md\:order-1 {
order: 1;
}
.md\:order-2 {
order: 2;
}
.md\:mt-0 {
margin-top: 0;
}
.md\:py-3 {
padding-top: var(--space-3);
padding-bottom: var(--space-3);
}
.md\:grid-cols-3 {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.md\:grid-cols-4 {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
}
@media (min-width: 1024px) {
.lg\:flex {
display: flex;
}
.lg\:hidden {
display: none;
}
.lg\:block {
display: block;
}
.lg\:px-8 {
padding-left: var(--space-8);
padding-right: var(--space-8);
}
.lg\:px-10 {
padding-left: var(--space-10);
padding-right: var(--space-10);
}
.lg\:flex-1 {
flex: 1 1 0;
}
.lg\:gap-x-12 {
column-gap: var(--space-12);
}
.lg\:grid-cols-4 {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.lg\:grid-cols-5 {
grid-template-columns: repeat(5, minmax(0, 1fr));
}
}

View File

@@ -1,97 +1,97 @@
/* Design Tokens */ /* Design Tokens */
:root { :root {
/* Colors */ /* Colors */
--color-black: #000000; --color-black: #000000;
--color-white: #ffffff; --color-white: #ffffff;
--color-slate-50: #f8fafc; --color-slate-50: #f8fafc;
--color-slate-100: #f1f5f9; --color-slate-100: #f1f5f9;
--color-slate-200: #e2e8f0; --color-slate-200: #e2e8f0;
--color-slate-300: #cbd5e1; --color-slate-300: #cbd5e1;
--color-slate-400: #94a3b8; --color-slate-400: #94a3b8;
--color-slate-500: #64748b; --color-slate-500: #64748b;
--color-slate-600: #475569; --color-slate-600: #475569;
--color-slate-700: #334155; --color-slate-700: #334155;
--color-slate-800: #1e293b; --color-slate-800: #1e293b;
--color-slate-900: #0f172a; --color-slate-900: #0f172a;
--color-gray-700: #374151; --color-gray-700: #374151;
--color-gray-800: #1f2937; --color-gray-800: #1f2937;
/* Typography */ /* Typography */
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--font-size-xs: 0.75rem; --font-size-xs: 0.75rem;
--font-size-sm: 0.875rem; --font-size-sm: 0.875rem;
--font-size-base: 1rem; --font-size-base: 1rem;
--font-size-lg: 1.125rem; --font-size-lg: 1.125rem;
--font-size-xl: 1.25rem; --font-size-xl: 1.25rem;
--font-size-2xl: 1.5rem; --font-size-2xl: 1.5rem;
--font-size-3xl: 1.875rem; --font-size-3xl: 1.875rem;
--font-size-4xl: 2.25rem; --font-size-4xl: 2.25rem;
--font-size-5xl: 3rem; --font-size-5xl: 3rem;
--font-size-6xl: 3.75rem; --font-size-6xl: 3.75rem;
--line-height-tight: 1.25; --line-height-tight: 1.25;
--line-height-normal: 1.5; --line-height-normal: 1.5;
--line-height-relaxed: 1.75; --line-height-relaxed: 1.75;
--font-weight-normal: 400; --font-weight-normal: 400;
--font-weight-medium: 500; --font-weight-medium: 500;
--font-weight-semibold: 600; --font-weight-semibold: 600;
--font-weight-bold: 700; --font-weight-bold: 700;
/* Spacing */ /* Spacing */
--space-0: 0; --space-0: 0;
--space-1: 0.25rem; --space-1: 0.25rem;
--space-2: 0.5rem; --space-2: 0.5rem;
--space-3: 0.75rem; --space-3: 0.75rem;
--space-4: 1rem; --space-4: 1rem;
--space-5: 1.25rem; --space-5: 1.25rem;
--space-6: 1.5rem; --space-6: 1.5rem;
--space-8: 2rem; --space-8: 2rem;
--space-10: 2.5rem; --space-10: 2.5rem;
--space-12: 3rem; --space-12: 3rem;
--space-16: 4rem; --space-16: 4rem;
--space-20: 5rem; --space-20: 5rem;
--space-24: 6rem; --space-24: 6rem;
/* Breakpoints */ /* Breakpoints */
--breakpoint-sm: 640px; --breakpoint-sm: 640px;
--breakpoint-md: 768px; --breakpoint-md: 768px;
--breakpoint-lg: 1024px; --breakpoint-lg: 1024px;
--breakpoint-xl: 1280px; --breakpoint-xl: 1280px;
--breakpoint-2xl: 1536px; --breakpoint-2xl: 1536px;
/* Container */ /* Container */
--container-max-width: 80rem; --container-max-width: 80rem;
/* Border Radius */ /* Border Radius */
--radius-sm: 0.125rem; --radius-sm: 0.125rem;
--radius-base: 0.25rem; --radius-base: 0.25rem;
--radius-md: 0.375rem; --radius-md: 0.375rem;
--radius-lg: 0.5rem; --radius-lg: 0.5rem;
--radius-xl: 0.75rem; --radius-xl: 0.75rem;
--radius-2xl: 1rem; --radius-2xl: 1rem;
--radius-full: 9999px; --radius-full: 9999px;
/* Z-index */ /* Z-index */
--z-base: 0; --z-base: 0;
--z-dropdown: 10; --z-dropdown: 10;
--z-sticky: 20; --z-sticky: 20;
--z-fixed: 30; --z-fixed: 30;
--z-overlay: 40; --z-overlay: 40;
--z-modal: 50; --z-modal: 50;
--z-popover: 60; --z-popover: 60;
--z-tooltip: 70; --z-tooltip: 70;
/* Transitions */ /* Transitions */
--transition-fast: 150ms; --transition-fast: 150ms;
--transition-base: 200ms; --transition-base: 200ms;
--transition-slow: 300ms; --transition-slow: 300ms;
--transition-slower: 500ms; --transition-slower: 500ms;
--transition-timing: cubic-bezier(0.4, 0, 0.2, 1); --transition-timing: cubic-bezier(0.4, 0, 0.2, 1);
/* Shadows */ /* Shadows */
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-base: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); --shadow-base: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
} }