Free
0
15
Tenace
By Mina Ortiz
Tenace is a bold, geometric education template for online bridge and card-game schools, with a shape mosaic hero, an interactive bidding quiz, a course spotlight, a weekly lesson calendar, progress rings and a searchable FAQ.
Download Codebase
Commercial License<%# letstemplates: access=free category=education %>
<!DOCTYPE html>
<html lang="en">
<head>
<script>
window.FontAwesomeConfig = { autoReplaceSvg: 'nest' };
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/js/all.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tenace | Tailwind CSS Education Template</title>
<meta name="description" content="Tenace is a bold, geometric Tailwind CSS education template for online bridge and card-game schools, with a shape mosaic hero, an interactive bidding quiz, a course spotlight, a weekly lesson calendar, progress rings and a searchable FAQ.">
<meta name="keywords" content="Tenace, Tailwind CSS template, HTML website template, education template, online course template, bridge school, card game lessons, e-learning platform, online academy">
<meta name="author" content="Let's templates">
<link rel="canonical" href="">
<meta property="og:type" content="product">
<meta property="og:site_name" content="Let's templates">
<meta property="og:locale" content="en_US">
<meta property="og:url" content="">
<meta property="og:title" content="Tenace | Tailwind CSS Education Template">
<meta property="og:description" content="A bold, geometric Tailwind CSS education template for online bridge schools, with an interactive bidding quiz, a course spotlight and a weekly lesson calendar.">
<meta property="og:image" content="">
<meta property="og:image:alt" content="Tenace template preview">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:url" content="">
<meta name="twitter:title" content="Tenace | Tailwind CSS Education Template">
<meta name="twitter:description" content="A bold, geometric Tailwind CSS education template for online bridge schools, with an interactive bidding quiz, a course spotlight and a weekly lesson calendar.">
<meta name="twitter:image" content="">
<meta name="twitter:image:alt" content="Tenace template preview">
<script src="https://cdn.tailwindcss.com/3.4.17"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=Unbounded:wght@600;800&display=swap" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
colors: { indigo: { DEFAULT: '#2f2fa2', deep: '#23238a', soft: '#6b5bd2' }, sun: '#fbbf3c', petal: '#f9b4c0', blush: '#f6e9e8', ink: '#141414', heart: '#d6324f' },
fontFamily: { sans: ['Inter', 'sans-serif'], display: ['Unbounded', 'sans-serif'] }
}
}
}
</script>
<style>
@view-transition { navigation: auto; }
::view-transition-old(root) { animation: 180ms ease-out both page-fade-out; }
::view-transition-new(root) { animation: 220ms ease-in both page-fade-in; }
@keyframes page-fade-out { to { opacity: 0; } }
@keyframes page-fade-in { from { opacity: 0; } }
html { scroll-behavior: smooth; }
[hidden] { display: none !important; }
.marquee { display: flex; overflow: hidden; gap: 3rem; }
.marquee-track { display: flex; flex-shrink: 0; align-items: center; gap: 3rem; animation: marquee 30s linear infinite; }
.marquee:hover .marquee-track { animation-play-state: paused; }
@keyframes marquee { to { transform: translateX(calc(-100% - 3rem)); } }
.ring-progress { transition: stroke-dashoffset 1.2s cubic-bezier(.2,.8,.2,1); }
.reveal-ready [data-ring] .ring-progress { stroke-dashoffset: 100; }
.reveal-ready [data-ring].is-visible .ring-progress { stroke-dashoffset: var(--ring-offset); }
.reveal-ready [data-reveal], .reveal-ready [data-tile] { opacity: 0; transform: translateY(24px); transition: opacity .6s ease, transform .6s ease; transition-delay: var(--reveal-delay, 0ms); }
.reveal-ready [data-tile] { transform: scale(.85); }
.reveal-ready [data-reveal].is-visible, .reveal-ready [data-tile].is-visible { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
.marquee-track { animation: none; }
.marquee { overflow-x: auto; }
::view-transition-old(root), ::view-transition-new(root) { animation: none; }
}
</style>
</head>
<body class="bg-blush font-sans text-ink antialiased">
<% active_page = "index" %>
<!-- block:start navbar -->
<header class="relative z-40">
<nav class="mx-auto flex max-w-[1600px] items-center justify-between gap-6 px-5 py-6 md:px-12 lg:py-8" aria-label="Main">
<div class="flex items-center gap-12">
<a href="index.html" class="font-display text-2xl font-extrabold tracking-tight text-indigo">TENACE</a>
<ul class="hidden items-center gap-6 text-sm uppercase lg:flex">
<% { "contacts" => "Contacts", "pricing" => "Choose a plan", "about" => "About us", "courses" => "Courses" }.each do |slug, label| %>
<% active = active_page == slug %>
<li><a href="<%= slug %>.html" <%= 'aria-current="page"'.html_safe if active %> class="<%= active ? 'font-semibold text-indigo underline decoration-2 underline-offset-8' : 'hover:text-indigo' %>"><%= label %></a></li>
<% end %>
</ul>
</div>
<div class="flex items-center gap-3">
<a href="login.html" class="hidden rounded-md bg-white px-8 py-3.5 text-sm font-semibold uppercase shadow-sm transition hover:bg-sun sm:inline-flex">Log in</a>
<button type="button" data-menu-toggle aria-controls="mobile-menu" aria-expanded="false" aria-label="Open menu" class="grid h-11 w-11 place-items-center rounded-md bg-indigo text-white lg:hidden"><i class="fa-solid fa-bars" aria-hidden="true"></i></button>
</div>
</nav>
<div id="mobile-menu" class="hidden px-5 pb-6 lg:hidden">
<ul class="grid gap-1 rounded-xl bg-white p-3 text-lg shadow-lg">
<% { "index" => "Home", "courses" => "Courses", "pricing" => "Choose a plan", "about" => "About us", "contacts" => "Contacts", "login" => "Log in" }.each do |slug, label| %>
<% active = active_page == slug %>
<li><a href="<%= slug %>.html" <%= 'aria-current="page"'.html_safe if active %> class="block rounded-lg px-4 py-2.5 <%= active ? 'bg-indigo font-semibold text-white' : 'hover:bg-blush' %>"><%= label %></a></li>
<% end %>
</ul>
</div>
</header>
<!-- block:end navbar -->
<% tiles = [
[:photo, "bg-sun", "rounded-full", "1438761681033-6461ffad8d80", "Young woman with auburn hair by a lake"],
[:shape, "bg-indigo-soft", "rounded-tl-full rounded-br-full"],
[:suit, "bg-indigo", "♥", "text-sun"],
[:photo, "bg-petal", "rounded-t-full", "1507003211169-0a1dd7228f2d", "Smiling man with a beard"],
[:shape, "bg-indigo-soft", "rounded-br-full"],
[:shape, "bg-sun", "rounded-tr-full rounded-bl-full"],
[:shape, "bg-indigo-soft", "rounded-full"],
[:shape, "bg-indigo-soft", "rounded-tl-full"],
[:shape, "bg-[#4b3fc4]", "rounded-b-full"],
[:brand, "bg-petal", "rounded-t-full"],
[:shape, "bg-indigo-soft", "rounded-tr-full"],
[:suit, "bg-indigo", "♦", "text-petal"],
[:photo, "bg-petal", "rounded-full", "1500648767791-00dcc994a43e", "Man in a grey sweater smiling"],
[:shape, "bg-indigo-soft", "rounded-bl-full"],
[:photo, "bg-sun", "rounded-t-full", "1573496359142-b8d87734a5a2", "Woman in a grey blazer smiling"],
[:shape, "bg-petal", "rounded-tl-full"],
[:shape, "bg-indigo-soft", "rounded-t-full"],
[:suit, "bg-indigo", "♣", "text-indigo-soft"],
[:shape, "bg-indigo-soft", "rounded-t-full"],
[:shape, "bg-petal", "rounded-t-full"]
] %>
<!-- block:start hero -->
<section class="relative overflow-hidden lg:-mt-[7rem] lg:min-h-[52rem]">
<div class="relative mx-auto grid max-w-[1600px] lg:min-h-[52rem] lg:grid-cols-[1.1fr_1fr]">
<div class="flex flex-col justify-center px-5 pb-12 pt-6 md:px-12 lg:pb-24 lg:pt-40">
<h1 class="font-display text-[1.9rem] font-extrabold uppercase leading-[1.08] tracking-tight sm:text-4xl md:text-5xl xl:text-[3.4rem]">
Learn <span class="text-indigo">bridge</span> by<br>playing real hands
</h1>
<p class="mt-6 text-xl font-light md:text-2xl">Sharpen every bid, lead and play</p>
<a href="courses.html" class="mt-9 inline-flex w-fit items-center rounded-md bg-indigo px-10 py-4 text-sm font-semibold uppercase tracking-wide text-white transition hover:bg-indigo-deep">Start learning</a>
<ul class="mt-16 flex gap-4 lg:mt-auto lg:pt-24">
<% [["fa-whatsapp", "WhatsApp", "https://www.whatsapp.com"], ["fa-facebook-f", "Facebook", "https://www.facebook.com"], ["fa-twitter", "Twitter", "https://twitter.com"]].each do |icon, label, url| %>
<li><a href="<%= url %>" aria-label="<%= label %>" class="grid h-10 w-10 place-items-center rounded-full bg-white text-indigo shadow-sm transition hover:bg-indigo hover:text-white"><i class="fa-brands <%= icon %>" aria-hidden="true"></i></a></li>
<% end %>
</ul>
</div>
<div class="relative bg-indigo px-5 pb-8 pt-8 md:px-12 lg:pb-0 lg:pt-36">
<div class="mx-auto grid max-w-xl grid-cols-4 lg:-ml-24 lg:max-w-none">
<% tiles.each_with_index do |(kind, bg, *rest), index| %>
<div data-tile style="--reveal-delay: <%= (index % 7) * 60 %>ms" class="relative aspect-square">
<% case kind %>
<% when :photo %>
<div class="h-full w-full overflow-hidden <%= bg %> <%= rest[0] %>"><img src="https://images.unsplash.com/photo-<%= rest[1] %>?auto=format&fit=crop&w=600&q=80" alt="<%= rest[2] %>" class="h-full w-full object-cover" loading="lazy"></div>
<% when :suit %>
<div class="grid h-full w-full place-items-center <%= bg %> font-display text-6xl md:text-7xl <%= rest[1] %>" aria-hidden="true"><%= rest[0] %></div>
<% when :brand %>
<div class="flex h-full w-full flex-col items-center justify-end pb-[12%] <%= bg %> <%= rest[0] %>">
<span class="text-5xl leading-none text-indigo md:text-6xl" aria-hidden="true">♠</span>
<span class="mt-2 font-display text-[0.6rem] font-extrabold text-indigo sm:text-xs">TENACE</span>
</div>
<% else %>
<div class="h-full w-full <%= bg %> <%= rest[0] %>" aria-hidden="true"></div>
<% end %>
</div>
<% end %>
</div>
</div>
</div>
</section>
<!-- block:end hero -->
<% hands = [
{ cards: [["K", "♠"], ["Q", "♠"], ["5", "♠"], ["A", "♥"], ["J", "♥"], ["9", "♥"], ["8", "♥"], ["4", "♥"], ["K", "♦"], ["6", "♦"], ["7", "♣"], ["3", "♣"], ["2", "♣"]], answer: "1♥", points: 13, why: "13 points and five hearts: open your longest suit. Five-card majors come first." },
{ cards: [["A", "♠"], ["Q", "♠"], ["4", "♠"], ["K", "♥"], ["10", "♥"], ["3", "♥"], ["Q", "♦"], ["J", "♦"], ["8", "♦"], ["2", "♦"], ["K", "♣"], ["9", "♣"], ["5", "♣"]], answer: "1NT", points: 15, why: "15 points and a balanced shape with no five-card major: that's a textbook 1NT." },
{ cards: [["J", "♠"], ["8", "♠"], ["6", "♠"], ["3", "♠"], ["Q", "♥"], ["7", "♥"], ["2", "♥"], ["K", "♦"], ["9", "♦"], ["4", "♦"], ["J", "♣"], ["10", "♣"], ["5", "♣"]], answer: "Pass", points: 7, why: "Only 7 points. Opening needs about 12, so pass and let partner speak." }
] %>
<!-- block:start demo -->
<section class="bg-indigo text-white" data-quiz>
<div class="mx-auto grid max-w-7xl gap-12 px-5 py-20 md:px-12 md:py-28 lg:grid-cols-[1fr_1.5fr] lg:items-center">
<div>
<p class="text-sm font-semibold uppercase tracking-widest text-sun">Try it now</p>
<h2 class="mt-3 font-display text-3xl font-extrabold uppercase leading-tight md:text-4xl">You're the dealer. What do you open?</h2>
<p class="mt-5 max-w-md text-lg font-light text-white/80">This is how every Tenace lesson works: a real hand, one decision, and instant feedback that explains the why.</p>
<p class="mt-8 text-sm text-white/60">Hand <span data-quiz-index>1</span> of <%= hands.size %> · Score <span data-quiz-score>0</span></p>
</div>
<div>
<% hands.each_with_index do |hand, index| %>
<div data-quiz-hand <%= 'hidden' unless index.zero? %> class="rounded-[2rem] bg-white/10 p-6 md:p-8">
<ul class="flex flex-wrap justify-center gap-1.5 sm:flex-nowrap sm:gap-0 sm:-space-x-6" aria-label="Your hand: <%= hand[:cards].map { |rank, suit| "#{rank}#{suit}" }.join(', ') %>">
<% hand[:cards].each_with_index do |(rank, suit), card_index| %>
<li class="grid h-[4.5rem] w-12 shrink-0 rounded-lg bg-white px-1.5 py-1 font-semibold shadow-lg ring-1 ring-black/10 transition hover:-translate-y-2 sm:h-24 sm:w-16 <%= %w[♥ ♦].include?(suit) ? 'text-heart' : 'text-ink' %>" style="transform: rotate(<%= (card_index - 6) * 2 %>deg)" aria-hidden="true">
<span class="text-sm leading-none sm:text-base"><%= rank %><br><%= suit %></span>
<span class="self-end justify-self-end text-xl leading-none sm:text-3xl"><%= suit %></span>
</li>
<% end %>
</ul>
<p class="mt-8 text-center text-white/80"><%= hand[:points] %> high-card points. Pick your opening bid:</p>
<div class="mt-4 grid grid-cols-3 gap-3" role="group" aria-label="Opening bid options">
<% ["1♥", "1NT", "Pass"].each do |bid| %>
<button type="button" data-quiz-option data-correct="<%= bid == hand[:answer] %>" class="rounded-xl bg-white py-4 font-display text-xl font-extrabold text-indigo transition hover:bg-sun aria-pressed:ring-4 aria-pressed:ring-sun"><%= bid %></button>
<% end %>
</div>
<div data-quiz-feedback class="mt-6 rounded-xl bg-white/10 p-5" role="status" aria-live="polite">
<p class="font-semibold"><span data-quiz-verdict>The answer is <%= hand[:answer] %>.</span></p>
<p class="mt-1 text-white/80"><%= hand[:why] %></p>
</div>
<button type="button" data-quiz-next class="mt-6 inline-flex items-center gap-2 rounded-md bg-sun px-6 py-3 text-sm font-semibold uppercase text-ink transition hover:bg-white">Next hand<i class="fa-solid fa-arrow-right" aria-hidden="true"></i></button>
</div>
<% end %>
</div>
</div>
</section>
<!-- block:end demo -->
<!-- block:start features -->
<section class="mx-auto max-w-7xl px-5 py-20 md:px-12 md:py-28">
<h2 class="max-w-3xl font-display text-3xl font-extrabold uppercase leading-tight md:text-4xl">One place to learn, practise and play</h2>
<div class="mt-12 grid gap-8 lg:grid-cols-[1fr_1.4fr] lg:items-center">
<div role="tablist" aria-label="How Tenace works" aria-orientation="vertical" data-tabs class="grid gap-3">
<% [["learn", "♠", "Learn", "Bite-size video lessons with a teacher who explains the why, not just the rule.", "bg-sun"], ["practise", "♥", "Practise", "Thousands of real deals with hints when you're stuck and replays when you're done.", "bg-petal"], ["play", "♦", "Play live", "Weekly small-group sessions and friendly online club nights.", "bg-indigo-soft text-white"], ["track", "♣", "Track progress", "Skill maps show what you've mastered and what to practise next.", "bg-sun"]].each_with_index do |(id, suit, title, text, tone), index| %>
<button type="button" role="tab" id="tab-<%= id %>" aria-controls="panel-<%= id %>" aria-selected="<%= index.zero? %>" tabindex="<%= index.zero? ? 0 : -1 %>" class="group flex gap-5 rounded-3xl p-5 text-left transition hover:bg-white/60 aria-selected:bg-white aria-selected:shadow-sm">
<span class="grid h-12 w-12 shrink-0 place-items-center rounded-tl-full rounded-br-full text-xl <%= tone %>" aria-hidden="true"><%= suit %></span>
<span><span class="block font-display text-lg font-extrabold uppercase"><%= title %></span><span class="mt-1 block font-light text-ink/70"><%= text %></span></span>
</button>
<% end %>
</div>
<div>
<% [["learn", "1543269664-56d93c1b41a6", "Smiling woman following a lesson on her tablet"], ["practise", "1541278107931-e006523892df", "Playing cards spread across a table"], ["play", "1511193311914-0346f16efe90", "Hand laying down cards on a green table"], ["track", "1522202176988-66273c2fd55f", "Students reviewing progress together on laptops"]].each_with_index do |(id, photo, alt), index| %>
<div id="panel-<%= id %>" role="tabpanel" aria-labelledby="tab-<%= id %>" <%= 'hidden' unless index.zero? %>>
<img src="https://images.unsplash.com/photo-<%= photo %>?auto=format&fit=crop&w=1200&q=80" alt="<%= alt %>" class="aspect-[4/3] w-full rounded-tl-[6rem] rounded-br-[6rem] object-cover" loading="lazy">
</div>
<% end %>
</div>
</div>
</section>
<!-- block:end features -->
<% courses = [
["Bidding Basics", "Beginner", "12 lessons · 4 weeks", "Count your points, open the bidding and find the right contract with your partner.", "1541278107931-e006523892df", "Playing cards spread across a table", "bg-sun", "bidding-basics.html"],
["Defence That Wins", "Intermediate", "10 lessons · 4 weeks", "Opening leads, signals and counting: the skills that turn good players into partners people want.", "1511193311914-0346f16efe90", "Hand laying down cards on a green table", "bg-petal", "courses.html"],
["Slam Bidding", "Intermediate", "9 lessons · 3 weeks", "Cue bids, key cards and the confidence to bid the big ones.", "1517245386807-bb43f82c33c4", "Two players discussing a hand at a laptop", "bg-indigo-soft text-white", "courses.html"],
["Squeezes & Endplays", "Advanced", "14 lessons · 6 weeks", "Make the defenders do your work for you in the final tricks.", "1576267423048-15c0040fec78", "Team celebrating a win around a table", "bg-sun", "courses.html"]
] %>
<!-- block:start products -->
<section class="bg-white" data-switcher>
<div class="mx-auto max-w-7xl px-5 py-20 md:px-12 md:py-28">
<div class="flex flex-wrap items-end justify-between gap-6">
<h2 class="font-display text-3xl font-extrabold uppercase md:text-4xl">Course spotlight</h2>
<a href="courses.html" class="text-sm font-semibold uppercase text-indigo underline underline-offset-4">All courses</a>
</div>
<div class="mt-12 grid gap-8 lg:grid-cols-[1.6fr_1fr]">
<div>
<% courses.each_with_index do |(title, level, meta, text, photo, alt, tone, href), index| %>
<article data-switch-panel="<%= index %>" <%= 'hidden' unless index.zero? %> class="grid overflow-hidden rounded-[2rem] bg-blush md:grid-cols-2">
<img src="https://images.unsplash.com/photo-<%= photo %>?auto=format&fit=crop&w=1200&q=80" alt="<%= alt %>" class="aspect-[4/3] h-full w-full object-cover md:aspect-auto" loading="lazy">
<div class="flex flex-col p-8">
<span class="w-fit rounded-md px-3 py-1 text-xs font-semibold uppercase <%= tone %>"><%= level %></span>
<h3 class="mt-5 font-display text-2xl font-extrabold uppercase"><%= title %></h3>
<p class="mt-2 text-sm text-ink/60"><%= meta %></p>
<p class="mt-4 flex-1 font-light text-ink/80"><%= text %></p>
<a href="<%= href %>" class="mt-8 inline-flex w-fit items-center rounded-md bg-indigo px-8 py-3.5 text-sm font-semibold uppercase text-white transition hover:bg-indigo-deep">View course</a>
</div>
</article>
<% end %>
</div>
<div role="tablist" aria-label="Choose a course" class="grid gap-3">
<% courses.each_with_index do |(title, level, meta, text, photo, alt), index| %>
<button type="button" role="tab" aria-selected="<%= index.zero? %>" data-switch="<%= index %>" class="flex items-center gap-4 rounded-2xl p-3 text-left transition hover:bg-blush aria-selected:bg-indigo aria-selected:text-white">
<img src="https://images.unsplash.com/photo-<%= photo %>?auto=format&fit=crop&w=300&q=80" alt="" class="h-16 w-16 shrink-0 rounded-tl-3xl rounded-br-3xl object-cover" loading="lazy">
<span><span class="block font-semibold"><%= title %></span><span class="text-sm opacity-70"><%= level %> · <%= meta %></span></span>
</button>
<% end %>
</div>
</div>
</div>
</section>
<!-- block:end products -->
<% week = [["Mon", 14], ["Tue", 15], ["Wed", 16], ["Thu", 17], ["Fri", 18], ["Sat", 19], ["Sun", 20]] %>
<% sessions = { "Mon" => [["18:00", "Opening leads that set traps", "bg-sun"]], "Wed" => [["12:30", "Lunch-break practice", "bg-petal"], ["19:30", "Counting the hand", "bg-indigo-soft text-white"]], "Thu" => [["18:00", "Ask the teacher", "bg-petal"]], "Fri" => [["18:00", "Defending no-trump", "bg-indigo-soft text-white"]], "Sun" => [["11:00", "Club night: duplicate", "bg-sun"], ["17:00", "Slam clinic", "bg-petal"]] } %>
<!-- block:start events -->
<section class="mx-auto max-w-7xl px-5 py-20 md:px-12 md:py-28">
<div class="flex flex-wrap items-end justify-between gap-6">
<h2 class="max-w-xl font-display text-3xl font-extrabold uppercase md:text-4xl">Live at the table this week</h2>
<p class="font-light text-ink/70">All times CET · 60 minutes · max 12 players</p>
</div>
<div class="mt-12 overflow-x-auto pb-2">
<ol class="grid min-w-[56rem] grid-cols-7 gap-3">
<% week.each do |day, date| %>
<li class="flex min-h-[16rem] flex-col rounded-3xl bg-white p-4 <%= 'ring-2 ring-indigo' if day == 'Wed' %>">
<p class="flex items-baseline justify-between"><span class="text-xs font-semibold uppercase text-ink/50"><%= day %></span><span class="font-display text-2xl font-extrabold"><%= date %></span></p>
<ul class="mt-4 grid gap-2">
<% (sessions[day] || []).each do |time, title, tone| %>
<li><a href="login.html" class="block rounded-2xl p-3 text-sm transition hover:-translate-y-0.5 hover:shadow-md <%= tone %>"><span class="block font-semibold"><%= time %></span><%= title %></a></li>
<% end %>
<% if sessions[day].nil? %><li class="rounded-2xl border-2 border-dashed border-indigo/15 p-3 text-sm text-ink/40">Self-study day</li><% end %>
</ul>
</li>
<% end %>
</ol>
</div>
</section>
<!-- block:end events -->
<!-- block:start stats -->
<section class="bg-indigo text-white">
<div class="mx-auto grid max-w-7xl gap-10 px-5 py-20 md:px-12 lg:grid-cols-[1fr_2fr] lg:items-center">
<h2 class="font-display text-3xl font-extrabold uppercase leading-tight md:text-4xl">Progress you can see</h2>
<dl class="grid gap-8 sm:grid-cols-3">
<% [[92, "92%", "pass their first level test", "#fbbf3c"], [78, "78%", "play at least weekly", "#f9b4c0"], [98, "4.9", "average lesson rating", "#ffffff"]].each_with_index do |(percent, label, text, color), index| %>
<div data-ring data-reveal style="--reveal-delay: <%= index * 120 %>ms" class="flex flex-col items-center text-center">
<dt class="order-2 mt-4 font-light text-white/80"><%= text %></dt>
<dd class="relative order-1 h-36 w-36">
<svg viewBox="0 0 120 120" class="h-full w-full -rotate-90" aria-hidden="true">
<circle cx="60" cy="60" r="52" fill="none" stroke="rgba(255,255,255,.15)" stroke-width="12"/>
<circle cx="60" cy="60" r="52" fill="none" stroke="<%= color %>" stroke-width="12" stroke-linecap="round" pathLength="100" stroke-dasharray="100" class="ring-progress" style="stroke-dashoffset: <%= 100 - percent %>; --ring-offset: <%= 100 - percent %>"/>
</svg>
<span class="absolute inset-0 grid place-items-center font-display text-3xl font-extrabold"><%= label %></span>
</dd>
</div>
<% end %>
</dl>
</div>
</section>
<!-- block:end stats -->
<% reviews = [["I'd watched my parents play for years. Three months in, I'm the one explaining squeezes at family dinners.", "Clara Jensen", "Player plan · learning since 2025", 1, "bg-sun"], ["The instant feedback is the magic. I finally understand why I lose tricks, not just that I lose them.", "Omar Haddad", "Player plan · intermediate", 3, "bg-petal"], ["Our club moved its beginner nights online with Tenace and membership doubled in a year.", "Helen Price", "Club organiser, Bath", 5, "bg-indigo-soft"], ["Live lessons are small enough that the teacher knows your name, and your bad habits.", "Ravi Mehta", "Club plan · advanced", 8, "bg-sun"]] %>
<!-- block:start reviews -->
<section class="mx-auto max-w-5xl px-5 py-20 text-center md:px-12 md:py-28" data-switcher>
<span class="font-display text-6xl text-indigo" aria-hidden="true">♥</span>
<% reviews.each_with_index do |(quote, name, role), index| %>
<figure data-switch-panel="<%= index %>" <%= 'hidden' unless index.zero? %>>
<blockquote class="mx-auto mt-6 max-w-3xl font-display text-2xl font-extrabold uppercase leading-snug md:text-3xl">“<%= quote %>”</blockquote>
<figcaption class="mt-6"><span class="font-semibold"><%= name %></span> <span class="font-light text-ink/60">· <%= role %></span></figcaption>
</figure>
<% end %>
<div role="tablist" aria-label="Choose a review" class="mt-10 flex justify-center gap-3">
<% reviews.each_with_index do |(quote, name, role, avatar, tone), index| %>
<button type="button" role="tab" aria-selected="<%= index.zero? %>" data-switch="<%= index %>" aria-label="Review by <%= name %>" class="h-16 w-16 overflow-hidden rounded-full opacity-50 ring-offset-4 ring-offset-blush transition hover:opacity-100 aria-selected:opacity-100 aria-selected:ring-4 aria-selected:ring-indigo <%= tone %>">
<img src="https://storage.googleapis.com/uxpilot-auth.appspot.com/avatars/avatar-<%= avatar %>.jpg" alt="" class="h-full w-full object-cover" loading="lazy">
</button>
<% end %>
</div>
</section>
<!-- block:end reviews -->
<!-- block:start pricing -->
<section id="plans" class="bg-white">
<div class="mx-auto grid max-w-7xl gap-6 px-5 py-20 md:px-12 md:py-28 lg:grid-cols-[1fr_1.6fr]">
<div class="flex flex-col rounded-[2rem] bg-blush p-8">
<h2 class="font-display text-xl font-extrabold uppercase">Free</h2>
<p class="mt-2 font-light text-ink/70">Learn the rules and play your first hands.</p>
<p class="mt-8 font-display text-5xl font-extrabold">$0</p>
<ul class="mt-8 grid flex-1 content-start gap-3 text-sm">
<% ["3 beginner courses", "50 practice deals", "Community forum"].each do |feature| %><li class="flex items-center gap-3"><i class="fa-solid fa-check text-indigo" aria-hidden="true"></i><%= feature %></li><% end %>
</ul>
<a href="login.html" class="mt-10 rounded-md border-2 border-indigo px-6 py-3.5 text-center text-sm font-semibold uppercase text-indigo transition hover:bg-indigo hover:text-white">Start free</a>
</div>
<div class="relative overflow-hidden rounded-[2rem] bg-indigo p-8 text-white md:p-12">
<div class="absolute -right-10 -top-10 grid grid-cols-2" aria-hidden="true"><span class="h-24 w-24 rounded-tl-full bg-sun"></span><span class="h-24 w-24 rounded-full bg-petal"></span><span class="h-24 w-24 rounded-br-full bg-indigo-soft"></span><span class="h-24 w-24 rounded-t-full bg-sun"></span></div>
<div class="relative max-w-md">
<span class="rounded-md bg-sun px-3 py-1 text-xs font-semibold uppercase text-ink">Most players choose this</span>
<h2 class="mt-6 font-display text-3xl font-extrabold uppercase">Player</h2>
<p class="mt-2 font-light text-white/80">Everything you need to improve every single week.</p>
<p class="mt-8 flex items-baseline gap-2"><span class="font-display text-6xl font-extrabold">$12</span><span class="text-white/70">/ month · or $120 a year</span></p>
<ul class="mt-8 grid gap-3 sm:grid-cols-2">
<% ["All courses", "Unlimited practice deals", "Weekly live lesson", "Progress reports", "Teacher Q&A", "Offline hands"].each do |feature| %><li class="flex items-center gap-3"><i class="fa-solid fa-check text-sun" aria-hidden="true"></i><%= feature %></li><% end %>
</ul>
<a href="login.html" class="mt-10 inline-flex items-center rounded-md bg-sun px-10 py-4 text-sm font-semibold uppercase text-ink transition hover:bg-white">Choose Player</a>
<p class="mt-4 text-sm text-white/70">Running a club? <a href="pricing.html" class="font-semibold text-white underline underline-offset-4">Compare all plans</a></p>
</div>
</div>
</div>
</section>
<!-- block:end pricing -->
<!-- block:start faq -->
<section class="mx-auto max-w-3xl px-5 py-20 md:px-12 md:py-28">
<h2 class="text-center font-display text-3xl font-extrabold uppercase md:text-4xl">Got a question?</h2>
<label class="mt-8 flex items-center gap-3 rounded-2xl border-2 border-indigo/15 bg-white px-5 py-4 focus-within:border-indigo">
<i class="fa-solid fa-magnifying-glass text-indigo" aria-hidden="true"></i>
<span class="sr-only">Search questions</span>
<input type="search" data-search-target="#faq-list" placeholder="Search, e.g. partner, phone, club…" class="w-full bg-transparent focus:outline-none">
</label>
<div id="faq-list" class="mt-6 grid gap-3">
<% [["I've never played bridge. Where do I start?", "Begin with Bidding Basics. It assumes no experience and gets you playing full hands by the second week."], ["Do I need a partner?", "No. Practice deals pair you with a computer partner, and live lessons match you with other students."], ["Can I learn on my phone?", "Yes. Lessons and practice deals work on any phone, tablet or computer, and your progress syncs between them."], ["Do you offer group rates for clubs?", "The Club plan covers up to 30 members. Larger clubs can ask us for a custom quote."], ["Which bidding system do you teach?", "Standard American and Acol, so you can play comfortably at clubs on both sides of the Atlantic."]].each do |question, answer| %>
<details data-search-item class="group rounded-2xl bg-white p-5">
<summary class="flex cursor-pointer list-none items-center justify-between gap-6 font-semibold"><%= question %><i class="fa-solid fa-plus shrink-0 text-indigo transition-transform group-open:rotate-45" aria-hidden="true"></i></summary>
<p class="mt-3 font-light text-ink/70"><%= answer %></p>
</details>
<% end %>
</div>
<p data-search-empty hidden class="mt-6 rounded-2xl bg-white p-5 text-center">No matches yet. <a href="contacts.html" class="font-semibold text-indigo underline underline-offset-4">Ask a teacher directly</a>.</p>
</section>
<!-- block:end faq -->
<!-- block:start cta -->
<section class="overflow-hidden bg-sun py-10" aria-label="Start for free">
<div class="marquee font-display text-5xl font-extrabold uppercase text-ink md:text-7xl">
<% 2.times do |copy| %>
<p class="marquee-track whitespace-nowrap" <%= 'aria-hidden="true"'.html_safe if copy == 1 %>>Play <span class="text-indigo">♠</span> Learn <span class="text-heart">♥</span> Win <span class="text-heart">♦</span> Repeat <span class="text-indigo">♣</span></p>
<% end %>
</div>
<div class="mt-8 text-center"><a href="login.html" class="inline-flex items-center rounded-md bg-indigo px-10 py-4 text-sm font-semibold uppercase text-white transition hover:bg-indigo-deep">Deal me in, it's free</a></div>
</section>
<!-- block:end cta -->
<!-- block:start footer -->
<footer class="bg-ink text-white">
<div class="mx-auto grid max-w-7xl gap-12 px-5 py-16 md:px-12 lg:grid-cols-[1.2fr_1fr]">
<div>
<a href="index.html" class="font-display text-2xl font-extrabold">TENACE</a>
<p class="mt-4 max-w-sm font-light text-white/60">The friendliest way to learn bridge online, from your first bid to your first tournament.</p>
<ul class="mt-8 flex flex-wrap gap-x-8 gap-y-3 text-white/70">
<li><a href="courses.html" class="hover:text-white">Courses</a></li>
<li><a href="pricing.html" class="hover:text-white">Plans</a></li>
<li><a href="about.html" class="hover:text-white">About us</a></li>
<li><a href="contacts.html" class="hover:text-white">Contacts</a></li>
<li><a href="login.html" class="hover:text-white">Log in</a></li>
</ul>
</div>
<form data-contact-form data-error="Please enter a valid email address." data-success="You're in! The weekly hand arrives every Sunday." class="rounded-3xl bg-white/5 p-6 md:p-8" novalidate>
<h2 class="font-display text-lg font-extrabold uppercase">The Sunday hand</h2>
<p class="mt-2 font-light text-white/60">One puzzle hand by email every week, with the answer on Monday.</p>
<div class="mt-5 flex flex-col gap-3 sm:flex-row">
<label class="sr-only" for="footer-email">Email address</label>
<input id="footer-email" type="email" name="email" required placeholder="you@example.com" autocomplete="email" class="min-w-0 flex-1 rounded-md bg-white px-4 py-3.5 text-ink focus:outline-none focus:ring-4 focus:ring-sun">
<button type="submit" class="rounded-md bg-sun px-6 py-3.5 text-sm font-semibold uppercase text-ink transition hover:bg-white">Subscribe</button>
</div>
<p data-form-status class="mt-3 text-sm text-white/80" role="status" aria-live="polite"></p>
</form>
</div>
<div class="mx-auto flex max-w-7xl flex-wrap items-center justify-between gap-4 border-t border-white/10 px-5 py-6 md:px-12">
<p class="text-sm text-white/50">© <span data-current-year>2026</span> Tenace Bridge School. All rights reserved.</p>
<ul class="flex gap-3">
<% [["fa-whatsapp", "WhatsApp", "https://www.whatsapp.com"], ["fa-facebook-f", "Facebook", "https://www.facebook.com"], ["fa-twitter", "Twitter", "https://twitter.com"]].each do |icon, label, url| %>
<li><a href="<%= url %>" aria-label="<%= label %>" class="grid h-10 w-10 place-items-center rounded-full bg-white/10 transition hover:bg-sun hover:text-ink"><i class="fa-brands <%= icon %>" aria-hidden="true"></i></a></li>
<% end %>
</ul>
</div>
</footer>
<!-- block:end footer -->
<script>
document.querySelectorAll('[data-menu-toggle]').forEach((button) => {
const menu = document.getElementById(button.getAttribute('aria-controls'));
if (!menu) return;
button.addEventListener('click', () => {
const open = button.getAttribute('aria-expanded') === 'true';
button.setAttribute('aria-expanded', String(!open));
menu.classList.toggle('hidden', open);
});
});
document.querySelectorAll('[data-quiz]').forEach((quiz) => {
const hands = [...quiz.querySelectorAll('[data-quiz-hand]')];
let current = 0;
let score = 0;
hands.forEach((hand) => {
const feedback = hand.querySelector('[data-quiz-feedback]');
const verdict = hand.querySelector('[data-quiz-verdict]');
const answer = verdict.textContent;
feedback.hidden = true;
hand.querySelectorAll('[data-quiz-option]').forEach((option) => {
option.addEventListener('click', () => {
if (!feedback.hidden) return;
const right = option.dataset.correct === 'true';
option.setAttribute('aria-pressed', 'true');
if (right) score += 1;
verdict.textContent = right ? `Spot on: ${option.textContent.trim()} is right.` : `Not quite. ${answer}`;
feedback.hidden = false;
quiz.querySelector('[data-quiz-score]').textContent = score;
});
});
hand.querySelector('[data-quiz-next]')?.addEventListener('click', () => {
hand.hidden = true;
current = (current + 1) % hands.length;
if (current === 0) { score = 0; quiz.querySelector('[data-quiz-score]').textContent = 0; }
const next = hands[current];
next.hidden = false;
next.querySelector('[data-quiz-feedback]').hidden = true;
next.querySelectorAll('[data-quiz-option]').forEach((option) => option.removeAttribute('aria-pressed'));
quiz.querySelector('[data-quiz-index]').textContent = current + 1;
next.querySelector('[data-quiz-option]')?.focus();
});
});
});
document.querySelectorAll('[data-tabs]').forEach((tablist) => {
const tabs = [...tablist.querySelectorAll('[role="tab"]')];
const select = (tab) => tabs.forEach((other) => {
const active = other === tab;
other.setAttribute('aria-selected', String(active));
other.tabIndex = active ? 0 : -1;
document.getElementById(other.getAttribute('aria-controls')).hidden = !active;
});
tabs.forEach((tab, index) => {
tab.addEventListener('click', () => select(tab));
tab.addEventListener('keydown', (event) => {
const step = { ArrowRight: 1, ArrowDown: 1, ArrowLeft: -1, ArrowUp: -1 }[event.key];
if (!step) return;
event.preventDefault();
const next = tabs[(index + step + tabs.length) % tabs.length];
next.focus();
select(next);
});
});
});
document.querySelectorAll('[data-switcher]').forEach((root) => {
root.addEventListener('click', (event) => {
const button = event.target.closest('[data-switch]');
if (!button) return;
root.querySelectorAll('[data-switch]').forEach((other) => other.setAttribute('aria-selected', String(other === button)));
root.querySelectorAll('[data-switch-panel]').forEach((panel) => { panel.hidden = panel.dataset.switchPanel !== button.dataset.switch; });
});
});
document.querySelectorAll('[data-search-target]').forEach((input) => {
const list = document.querySelector(input.dataset.searchTarget);
const empty = list?.parentElement.querySelector('[data-search-empty]');
input.addEventListener('input', () => {
const query = input.value.trim().toLowerCase();
let shown = 0;
list.querySelectorAll('[data-search-item]').forEach((item) => {
const match = !query || item.textContent.toLowerCase().includes(query);
item.hidden = !match;
if (match) shown += 1;
});
if (empty) empty.hidden = shown > 0;
});
});
document.querySelectorAll('[data-course-tools]').forEach((tools) => {
const grid = document.querySelector(tools.dataset.courseTools);
const search = tools.querySelector('[data-course-search]');
const sort = tools.querySelector('[data-course-sort]');
const count = tools.querySelector('[data-course-count]');
const apply = () => {
const query = search.value.trim().toLowerCase();
const items = [...grid.querySelectorAll('[data-course]')];
items.sort((a, b) => Number(a.dataset[sort.value]) - Number(b.dataset[sort.value])).forEach((item) => grid.append(item));
let shown = 0;
items.forEach((item) => {
const match = !query || item.textContent.toLowerCase().includes(query);
item.hidden = !match;
if (match) shown += 1;
});
count.textContent = shown;
};
search.addEventListener('input', apply);
sort.addEventListener('change', apply);
});
document.querySelectorAll('[data-booking]').forEach((form) => {
const summary = form.querySelector('[data-booking-summary]');
const update = () => {
const day = [...form.querySelectorAll('[name="day"]')].find((input) => input.checked);
const time = [...form.querySelectorAll('[name="time"]')].find((input) => input.checked);
summary.textContent = day && time ? `${day.value} at ${time.value}` : 'Pick a day and a time';
};
form.addEventListener('change', update);
update();
});
document.querySelectorAll('[data-contact-form], [data-booking]').forEach((form) => {
form.addEventListener('submit', (event) => {
event.preventDefault();
const status = form.querySelector('[data-form-status]');
const groups = [...form.querySelectorAll('[data-required-group]')].every((group) => [...group.querySelectorAll('[name]')].some((choice) => choice.checked));
if (!groups || !form.checkValidity()) {
status.textContent = form.dataset.error;
return;
}
status.textContent = form.dataset.success;
form.reset();
form.dispatchEvent(new Event('change'));
});
});
document.querySelectorAll('[data-current-year]').forEach((el) => { el.textContent = new Date().getFullYear(); });
(() => {
const items = document.querySelectorAll('[data-reveal], [data-tile]');
const skip = navigator.webdriver || !('IntersectionObserver' in window) || matchMedia('(prefers-reduced-motion: reduce)').matches;
if (!items.length || skip) return;
document.documentElement.classList.add('reveal-ready');
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
entry.target.classList.add('is-visible');
observer.unobserve(entry.target);
});
}, { rootMargin: '0px 0px -10% 0px' });
items.forEach((item) => observer.observe(item));
})();
</script>
</body>
</html>
Unlimited access to all templates
Unlock our entire library of premium UI kits, dashboards, and landing pages. Renews automatically, cancel anytime.
$20
/ 3 months
or $60 / year
Get Unlimited Access