Free
0
1
Oruna
By Avery Cole
Oruna is a dark, neon-lime health and fitness template for AI coaching and wearable apps, with an AI prompt hero, phone mockups, an animated run-route replay, activity rings, features, technology, community, programs, pricing and contact pages.
- CrossFit & Training Studio
- Gym
- Personal Trainer
- Yoga & Pilates Studio
- Sports Club
- Sportswear & Sports Gear
- Medical Clinic
- Therapy & Mental Health
- Physiotherapy
- Nutritionist & Healthy Food
- Health & Fitness App
Download Codebase
Commercial License<%# letstemplates: access=premium category=health-fitness %>
<!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>Oruna | Tailwind CSS Health & Fitness Template</title>
<meta name="description" content="Oruna is a dark, neon-lime Tailwind CSS health and fitness template for AI coaching and wearable apps, with an AI prompt hero, phone mockups, an animated run-route replay, activity rings, features, technology, community, programs, pricing and contact pages.">
<meta name="keywords" content="Oruna, Tailwind CSS template, HTML website template, fitness app template, AI coach, wellness app, running tracker, wearable app landing page, health app, app download page">
<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="Oruna | Tailwind CSS Health & Fitness Template">
<meta property="og:description" content="A neon-lime Tailwind CSS fitness app template with an AI prompt hero, phone mockups, a live run-route replay and activity rhythm cards.">
<meta property="og:image" content="">
<meta property="og:image:alt" content="Oruna template preview">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:url" content="">
<meta name="twitter:title" content="Oruna | Tailwind CSS Health & Fitness Template">
<meta name="twitter:description" content="A neon-lime Tailwind CSS fitness app template with an AI prompt hero, phone mockups, a live run-route replay and activity rhythm cards.">
<meta name="twitter:image" content="">
<meta name="twitter:image:alt" content="Oruna 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@400;500;600&family=Instrument+Serif:ital@0;1&display=swap" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
colors: { lime: { DEFAULT: '#c8ee5c', deep: '#a9d13b', soft: '#e4f7a8' }, ink: { DEFAULT: '#0c0c0c', card: '#151515', line: '#262626' }, ember: '#ff8a3d', fog: '#d6d8d7' },
fontFamily: { sans: ['Inter', 'sans-serif'], serif: ['"Instrument Serif"', '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; }
.map-grid { background-image: linear-gradient(#1d1d1d 1px, transparent 1px), linear-gradient(90deg, #1d1d1d 1px, transparent 1px), linear-gradient(#232323 3px, transparent 3px), linear-gradient(90deg, #232323 3px, transparent 3px); background-size: 64px 64px, 64px 64px, 256px 256px, 256px 256px; transform: rotate(-14deg) scale(1.5); }
.glow { filter: drop-shadow(0 0 10px rgba(200, 238, 92, .75)); }
.reveal-ready [data-reveal] { opacity: 0; transform: translateY(24px); transition: opacity .6s ease, transform .6s ease; transition-delay: var(--reveal-delay, 0ms); }
.reveal-ready [data-reveal].is-visible { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
::view-transition-old(root), ::view-transition-new(root) { animation: none; }
}
</style>
</head>
<body class="bg-ink font-sans text-white antialiased">
<% active_page = "index" %>
<% nav = { "index" => "Overview", "features" => "Features", "technology" => "Technology", "community" => "Community" } %>
<!-- block:start navbar -->
<header class="fixed inset-x-0 top-0 z-40 px-4 pt-4" data-navbar>
<nav class="mx-auto flex max-w-3xl items-center justify-between gap-4 rounded-full border border-white/40 bg-white/25 py-2 pl-4 pr-2 text-ink shadow-lg shadow-ink/5 backdrop-blur-xl transition-all duration-300 data-[scrolled=true]:max-w-2xl data-[scrolled=true]:border-white/10 data-[scrolled=true]:bg-ink/80 data-[scrolled=true]:text-white" aria-label="Main" data-nav-pill data-solid="<%= active_page != 'index' %>" data-scrolled="<%= active_page != 'index' %>">
<a href="index.html" class="flex items-center gap-2 text-base font-semibold"><svg viewBox="0 0 32 32" class="h-6 w-6" aria-hidden="true"><path d="M4 18C4 10 10 4 18 4c0 8-6 14-14 14Z" fill="#c8ee5c"/><path d="M28 14c0 8-6 14-14 14 0-8 6-14 14-14Z" fill="#a9d13b"/></svg>Oruna</a>
<ul class="hidden items-center gap-1 text-sm md:flex">
<% nav.each do |slug, label| %>
<% active = active_page == slug %>
<li><a href="<%= slug %>.html" <%= 'aria-current="page"'.html_safe if active %> class="rounded-full px-3.5 py-2 transition <%= active ? 'bg-ink/10 font-medium' : 'opacity-70 hover:opacity-100' %>"><%= label %></a></li>
<% end %>
</ul>
<div class="flex items-center gap-2">
<a href="pricing.html" class="flex items-center gap-2 rounded-full bg-ink px-4 py-2.5 text-sm font-medium text-white ring-1 ring-white/10 transition hover:bg-lime hover:text-ink">Download<i class="fa-solid fa-arrow-down text-xs" aria-hidden="true"></i></a>
<button type="button" data-menu-toggle aria-controls="mobile-menu" aria-expanded="false" class="grid h-10 w-10 place-items-center rounded-full bg-ink/10 md:hidden" aria-label="Open menu"><i class="fa-solid fa-bars" aria-hidden="true"></i></button>
</div>
</nav>
<div id="mobile-menu" class="mx-auto mt-2 hidden max-w-3xl rounded-3xl bg-ink p-3 text-white shadow-2xl md:hidden">
<ul class="grid gap-1">
<% nav.merge("programs" => "Programs", "pricing" => "Pricing", "contact" => "Contact").each do |slug, label| %>
<% active = active_page == slug %>
<li><a href="<%= slug %>.html" <%= 'aria-current="page"'.html_safe if active %> class="block rounded-2xl px-4 py-3 <%= active ? 'bg-lime text-ink' : 'hover:bg-white/10' %>"><%= label %></a></li>
<% end %>
</ul>
</div>
</header>
<!-- block:end navbar -->
<% moods = [["energize", "Energize", "fa-bolt", "Let's wake you up: a 20-minute tempo run with three surges, then a protein-first breakfast."], ["recover", "Recover", "fa-plus", "Your sleep dipped last night. Try a 25-minute easy walk and ten minutes of mobility instead of intervals."], ["focus", "Focus", "fa-brain", "A steady 30-minute zone 2 ride keeps your mind clear before a busy afternoon."], ["calm", "Calm", "fa-heart", "Box breathing for four minutes, then a slow yoga flow. Nothing to chase today."], ["reset", "Reset", "fa-rotate-right", "Fresh week, fresh baseline. I'll run a short check-in and rebuild your plan from today."]] %>
<!-- block:start hero -->
<section class="relative isolate overflow-hidden bg-lime text-ink">
<img src="https://images.unsplash.com/photo-1552196563-55cd4e45efb3?auto=format&fit=crop&w=1800&q=80" alt="Woman sitting cross-legged on a mat against a bright studio wall" class="absolute inset-x-0 top-0 -z-10 h-[78%] w-full object-cover object-[50%_35%] grayscale">
<div class="absolute inset-0 -z-10 bg-gradient-to-b from-fog/30 via-fog/10 via-40% to-lime to-[78%]"></div>
<div class="mx-auto flex min-h-[54rem] max-w-4xl flex-col items-center justify-end px-5 pb-16 pt-40 text-center md:min-h-[60rem]">
<h1 class="font-serif text-6xl leading-[.95] text-white drop-shadow-[0_4px_24px_rgba(12,12,12,.45)] md:text-8xl lg:text-9xl" data-reveal>Train With <em>Intention</em></h1>
<p class="mt-5 max-w-lg text-base font-medium text-white drop-shadow-[0_2px_12px_rgba(12,12,12,.5)] md:text-lg" data-reveal style="--reveal-delay: 80ms">Oruna is the AI coach that hears how you feel, learns your patterns and adjusts, so your body settles into a rhythm that lasts.</p>
<div class="mt-9 w-full max-w-xl rounded-3xl border border-white/40 bg-white/20 p-2 text-left shadow-2xl shadow-ink/10 backdrop-blur-xl" data-coach data-reveal style="--reveal-delay: 160ms">
<form class="rounded-2xl border border-white/50 bg-white/30 p-3" data-coach-form novalidate>
<label for="coach-input" class="sr-only">Tell Oruna how you feel</label>
<textarea id="coach-input" rows="2" class="w-full resize-none bg-transparent text-sm text-ink placeholder:text-ink/60 focus:outline-none" placeholder="Morning! How's your energy, and how would you like to move today?" data-coach-input></textarea>
<div class="mt-2 flex items-center justify-between">
<button type="button" class="grid h-8 w-8 place-items-center rounded-full border border-ink/20 text-xs" aria-label="Attach a workout"><i class="fa-solid fa-plus" aria-hidden="true"></i></button>
<span class="flex gap-1.5">
<button type="button" class="grid h-8 w-8 place-items-center rounded-full bg-lime text-xs text-ink" aria-label="Speak to Oruna"><i class="fa-solid fa-microphone" aria-hidden="true"></i></button>
<button type="submit" class="grid h-8 w-8 place-items-center rounded-full bg-lime text-xs text-ink transition hover:bg-lime-deep" aria-label="Send"><i class="fa-solid fa-arrow-up" aria-hidden="true"></i></button>
</span>
</div>
</form>
<div class="flex flex-wrap gap-1.5 p-2" role="group" aria-label="Quick moods">
<% moods.each do |(key, label, icon)| %>
<button type="button" data-mood="<%= key %>" aria-pressed="false" class="flex items-center gap-1.5 rounded-full border border-ink/15 bg-white/30 px-3 py-1.5 text-xs font-medium transition hover:bg-white/60 aria-pressed:border-ink aria-pressed:bg-ink aria-pressed:text-lime"><i class="fa-solid <%= icon %> text-[10px]" aria-hidden="true"></i><%= label %></button>
<% end %>
</div>
<p class="mx-2 mb-2 rounded-2xl bg-ink px-4 py-3 text-sm text-white" data-coach-reply aria-live="polite" hidden></p>
<script type="application/json" data-coach-data><%= raw moods.to_h { |(key, label, _icon, reply)| [key, { label: label, reply: reply }] }.to_json %></script>
</div>
</div>
</section>
<!-- block:end hero -->
<!-- block:start integrations -->
<section class="bg-lime text-ink" aria-label="Works with your devices">
<ul class="mx-auto flex max-w-4xl flex-wrap items-center justify-center gap-x-10 gap-y-5 px-5 pb-16 opacity-70">
<% [["fa-heart-pulse", "Pulsewear"], ["fa-shoe-prints", "STRIDELAB"], ["fa-wave-square", "northbeat"], ["fa-ring", "Ringly"], ["fa-spa", "calmfield"], ["fa-stopwatch", "TEMPO ONE"]].each do |(icon, name)| %>
<li class="flex items-center gap-2 text-xl font-semibold tracking-tight"><i class="fa-solid <%= icon %> text-lg" aria-hidden="true"></i><%= name %></li>
<% end %>
</ul>
</section>
<!-- block:end integrations -->
<!-- block:start about -->
<section id="overview" class="relative z-10 bg-lime text-ink">
<div class="mx-auto max-w-3xl px-5 pt-6 text-center">
<p class="mx-auto w-fit rounded-full border border-ink/10 bg-lime-soft px-3 py-1 text-xs font-medium shadow-sm">AI Wellness Companion</p>
<p class="mt-8 font-serif text-3xl leading-tight md:text-5xl" data-reveal><em>Oruna</em> pairs the accuracy of a wearable with the instinct of a great coach, so you train smarter, eat better and recover fully.</p>
</div>
<div class="relative mx-auto mt-12 h-[19rem] w-[17.5rem] md:h-[23rem]">
<div class="absolute inset-x-0 top-0 rounded-[3rem] border-[6px] border-zinc-300 bg-ink p-3 text-white shadow-[0_40px_80px_-20px_rgba(12,12,12,.6)]">
<div class="flex items-center justify-between px-3 pt-1 text-[11px] font-semibold"><span>9:41</span><span class="h-5 w-20 rounded-full bg-black"></span><span class="flex gap-1"><i class="fa-solid fa-signal" aria-hidden="true"></i><i class="fa-solid fa-battery-full" aria-hidden="true"></i></span></div>
<div class="mt-3 flex items-start justify-between px-2"><div><p class="font-serif text-2xl leading-none">Today</p><p class="mt-1 text-[10px] text-white/50">Tuesday, 14 April</p></div><img src="https://storage.googleapis.com/uxpilot-auth.appspot.com/avatars/avatar-2.jpg" alt="" class="h-9 w-9 rounded-full border-2 border-lime object-cover"></div>
<div class="relative mx-auto mt-4 w-44">
<svg viewBox="0 0 200 110" class="w-full" aria-hidden="true"><path d="M20 100a80 80 0 0 1 160 0" fill="none" stroke="#262626" stroke-width="14" stroke-linecap="round"/><path d="M20 100a80 80 0 0 1 160 0" fill="none" stroke="#c8ee5c" stroke-width="14" stroke-linecap="round" pathLength="100" stroke-dasharray="78 100"/></svg>
<p class="absolute inset-x-0 bottom-0 text-center"><span class="block font-serif text-5xl leading-none">78</span><span class="text-[10px] text-white/60">Readiness</span></p>
</div>
<div class="mt-4 grid grid-cols-2 gap-2">
<% [["fa-fire", "Move", "310", "/ 750 kcal", 8], ["fa-person-running", "Train", "42", "/ 60 min", 13]].each do |(icon, label, value, goal, filled)| %>
<div class="rounded-2xl bg-ink-card p-3">
<p class="flex items-center gap-1.5 text-[10px] text-white/60"><i class="fa-solid <%= icon %>" aria-hidden="true"></i><%= label %></p>
<p class="mt-1 text-right font-serif text-3xl leading-none"><%= value %></p><p class="text-right text-[9px] text-white/50"><%= goal %></p>
<p class="mt-2 flex justify-between" aria-hidden="true"><% 18.times do |i| %><span class="h-5 w-[3px] rounded-full <%= i < filled - 1 ? 'bg-lime' : (i == filled - 1 ? 'bg-ember' : 'bg-white/10') %>"></span><% end %></p>
</div>
<% end %>
</div>
<div class="mt-2 flex items-end justify-between rounded-2xl bg-ink-card p-3"><p class="text-[10px] text-white/60"><i class="fa-solid fa-shoe-prints" aria-hidden="true"></i> Tuesday Tempo Run</p><p class="font-serif text-3xl leading-none">6,20<span class="ml-1 font-sans text-[9px] text-white/50">km</span></p></div>
<div class="mx-auto mt-3 flex w-fit gap-3 rounded-full bg-ink-card px-3 py-2 text-[11px]" aria-hidden="true"><span class="grid h-8 w-8 place-items-center rounded-full bg-lime text-ink"><i class="fa-solid fa-heart"></i></span><span class="grid h-8 w-8 place-items-center text-white/60"><i class="fa-solid fa-person-running"></i></span><span class="grid h-8 w-8 place-items-center text-white/60"><i class="fa-solid fa-circle-dot"></i></span><span class="grid h-8 w-8 place-items-center text-white/60"><i class="fa-solid fa-comment"></i></span></div>
</div>
</div>
</section>
<!-- block:end about -->
<% routes = [["morning", "Morning loop", "M70 250 L170 180 L270 250 L340 228 L420 110 L560 190 L510 250 L640 96", [640, 96], "38:14", 412, "6,20", 142], ["river", "River tempo", "M60 210 C160 110 240 290 360 200 S560 110 640 190 L740 120", [740, 120], "52:40", 598, "9,45", 156], ["hills", "Hill repeats", "M100 262 L200 120 L260 240 L340 110 L420 240 L500 100 L580 240 L660 92", [660, 92], "29:05", 365, "4,10", 168]] %>
<% first = routes.first %>
<!-- block:start demo -->
<section id="route" class="relative overflow-hidden bg-ink pb-24 pt-64 md:pt-80" data-route>
<div class="map-grid pointer-events-none absolute -inset-1/4 opacity-80" aria-hidden="true"></div>
<div class="pointer-events-none absolute inset-x-0 top-0 h-40 bg-gradient-to-b from-ink to-transparent" aria-hidden="true"></div>
<div class="pointer-events-none absolute inset-x-0 bottom-0 h-56 bg-gradient-to-t from-ink to-transparent" aria-hidden="true"></div>
<div class="relative mx-auto max-w-5xl px-5">
<div class="mx-auto flex w-fit flex-wrap justify-center gap-2" role="tablist" aria-label="Sample runs">
<% routes.each_with_index do |(key, label), index| %>
<button type="button" role="tab" data-route-tab="<%= key %>" aria-selected="<%= index.zero? %>" class="rounded-full border border-white/15 px-4 py-2 text-sm text-white/70 transition hover:border-white/40 aria-selected:border-lime aria-selected:bg-lime aria-selected:text-ink"><%= label %></button>
<% end %>
</div>
<svg viewBox="0 0 800 320" class="mx-auto mt-8 w-full max-w-3xl overflow-visible" role="img" aria-label="Route of the selected run drawn on a city map">
<defs><clipPath id="pin-clip"><circle r="18"/></clipPath></defs>
<path d="<%= first[2] %>" fill="none" stroke="#c8ee5c" stroke-width="7" stroke-linejoin="round" stroke-linecap="round" class="glow" data-route-path/>
<g data-route-start transform="translate(70 250)"><circle r="26" fill="#c8ee5c" opacity=".18"/><circle r="11" fill="#c8ee5c" class="glow"/></g>
<g data-route-pin transform="translate(<%= first[3].join(' ') %>)"><circle r="40" fill="#c8ee5c" opacity=".15"/><circle r="23" fill="#c8ee5c"/><image href="https://storage.googleapis.com/uxpilot-auth.appspot.com/avatars/avatar-2.jpg" x="-18" y="-18" width="36" height="36" clip-path="url(#pin-clip)" preserveAspectRatio="xMidYMid slice"/></g>
</svg>
<div class="mt-10 text-center">
<p class="font-serif text-7xl leading-none md:text-8xl" data-route-time><%= first[4] %></p>
<p class="mt-1 text-sm text-white/60">Duration</p>
<dl class="mx-auto mt-8 grid max-w-sm grid-cols-3 gap-4">
<% [["fa-bolt", "kcal", "Kcal", first[5]], ["fa-location-dot", "km", "Km", first[6]], ["fa-heart", "bpm", "Bpm", first[7]]].each do |(icon, key, label, value)| %>
<div><dt class="text-xs text-white/50"><i class="fa-solid <%= icon %> mb-2 block text-ember" aria-hidden="true"></i><span class="sr-only"><%= label %></span></dt><dd class="font-serif text-4xl" data-route-stat="<%= key %>"><%= value %></dd><dd class="text-xs text-white/50" aria-hidden="true"><%= label %></dd></div>
<% end %>
</dl>
<button type="button" data-route-play class="mx-auto mt-10 flex items-center gap-2 rounded-full bg-lime px-5 py-3 text-sm font-medium text-ink shadow-[0_0_30px_rgba(200,238,92,.45)] transition hover:bg-lime-deep"><i class="fa-solid fa-play text-xs" aria-hidden="true"></i>Replay run</button>
</div>
</div>
<script type="application/json" data-route-data><%= raw routes.to_h { |(key, _label, d, pin, time, kcal, km, bpm)| [key, { d: d, pin: pin, time: time, kcal: kcal, km: km, bpm: bpm }] }.to_json %></script>
</section>
<!-- block:end demo -->
<!-- block:start features -->
<section id="features" class="bg-ink py-24 md:py-32">
<div class="mx-auto max-w-5xl px-5">
<div class="mx-auto max-w-xl text-center">
<p class="mx-auto w-fit rounded-full border border-white/20 px-3 py-1 text-xs font-medium">Why Oruna</p>
<h2 class="mt-5 font-serif text-5xl leading-[.95] md:text-7xl">Wellness That <em>Gets</em> You</h2>
<p class="mt-5 text-white/60">An AI coach that listens first, then shapes each day around your energy, sleep and schedule.</p>
</div>
<ul class="mt-16 grid gap-8 md:grid-cols-3 md:gap-4">
<% [["A Coach That Hears You", "Every plan begins with how you feel this morning, not with a template.", "1506794778202-cad84cf45f1d", "Portrait of a man looking calmly at the camera"], ["Steady Over Strained", "Oruna builds consistency through rest, recovery and clear intent.", "1506126613408-eca07ce68773", "Silhouette of a person meditating at sunrise"], ["Pairs With Anything", "Works with your watch, ring or band, or simply with your phone.", "1434494878577-86c23bcb06b9", "Person checking a smartwatch on their wrist"]].each_with_index do |(title, text, photo, alt), index| %>
<li data-reveal style="--reveal-delay: <%= index * 100 %>ms">
<img src="https://images.unsplash.com/photo-<%= photo %>?auto=format&fit=crop&w=700&h=700&q=80" alt="<%= alt %>" class="aspect-square w-full object-cover grayscale">
<h3 class="mt-8 max-w-[10rem] font-serif text-3xl leading-none"><%= title %></h3>
<p class="mt-4 max-w-xs text-sm text-white/60"><%= text %></p>
</li>
<% end %>
</ul>
</div>
</section>
<!-- block:end features -->
<% rhythm = { "today" => [["fa-fire", "Move", 310, "750 kcal", 750], ["fa-person-running", "Train", 42, "60 min", 60], ["fa-bullseye", "Monthly Challenge", "86,40", "160 km", 160, 86.4]], "week" => [["fa-fire", "Move", 4210, "5,250 kcal", 5250], ["fa-person-running", "Train", 318, "420 min", 420], ["fa-bullseye", "Monthly Challenge", "112,70", "160 km", 160, 112.7]] } %>
<!-- block:start stats -->
<section class="bg-ink pb-24 md:pb-32" data-rhythm>
<div class="mx-auto max-w-3xl px-5">
<div class="text-center">
<p class="mx-auto w-fit rounded-full border border-white/20 px-3 py-1 text-xs font-medium">Core Experience</p>
<h2 class="mt-5 font-serif text-5xl leading-[.95] md:text-7xl">Every Day, One<br><em>Rhythm</em></h2>
<p class="mt-4 text-sm text-white/60">Training, rest and reflection, gently steered by Oruna.</p>
<div class="mx-auto mt-8 flex w-fit gap-1 rounded-full bg-ink-card p-1" role="tablist" aria-label="Period">
<% %w[today week].each_with_index do |key, index| %>
<button type="button" role="tab" data-period="<%= key %>" aria-selected="<%= index.zero? %>" class="rounded-full px-5 py-2 text-sm text-white/60 transition aria-selected:bg-white aria-selected:text-ink"><%= key == "today" ? "Today" : "This week" %></button>
<% end %>
</div>
</div>
<div class="mt-10 grid gap-4 md:grid-cols-2">
<% rhythm["today"].each_with_index do |(icon, label, value, goal, max, amount), index| %>
<% ticks = index == 2 ? 30 : 15 %>
<% filled = ((amount || value).to_f / max * ticks).round %>
<article class="rounded-3xl border border-white/5 bg-ink-card p-6 <%= 'md:col-span-2' if index == 2 %>" data-rhythm-card="<%= index %>" data-reveal style="--reveal-delay: <%= index * 80 %>ms">
<h3 class="flex items-center gap-2 text-sm text-white/70"><i class="fa-solid <%= icon %> text-white" aria-hidden="true"></i><%= label %></h3>
<p class="mt-3 text-right font-serif text-6xl leading-none" data-rhythm-value><%= value %></p>
<p class="text-right text-sm text-white/50" data-rhythm-goal>/ <%= goal %></p>
<p class="mt-6 flex justify-between" data-rhythm-ticks aria-hidden="true">
<% ticks.times do |i| %><span class="h-16 w-1.5 rounded-full transition-colors <%= i < filled - 1 ? 'bg-lime' : (i == filled - 1 ? 'bg-ember shadow-[0_0_12px_#ff8a3d]' : 'bg-white/5') %>"></span><% end %>
</p>
</article>
<% end %>
</div>
</div>
<script type="application/json" data-rhythm-data><%= raw rhythm.transform_values { |cards| cards.map { |(_icon, _label, value, goal, max, amount)| { value: value, goal: goal, ratio: (amount || value).to_f / max } } }.to_json %></script>
</section>
<!-- block:end stats -->
<!-- block:start cta -->
<section id="download" class="overflow-hidden bg-ink pt-12">
<div class="mx-auto max-w-4xl px-5 text-center">
<p class="mx-auto w-fit rounded-full border border-white/20 px-3 py-1 text-xs font-medium">Get Started</p>
<h2 class="mt-5 font-serif text-5xl leading-[.95] md:text-8xl">Your Strongest<br>Chapter <em>Starts</em> Today</h2>
<a href="pricing.html" class="mx-auto mt-8 flex w-fit items-center gap-2 rounded-full bg-lime px-5 py-3 text-sm font-semibold text-ink shadow-[0_0_30px_rgba(200,238,92,.45)] transition hover:bg-lime-deep">Get the app<i class="fa-solid fa-arrow-down text-xs" aria-hidden="true"></i></a>
</div>
<div class="relative mx-auto mt-16 flex max-w-3xl justify-center gap-4 px-5 md:gap-10" aria-hidden="true">
<div class="w-56 shrink-0 -rotate-[8deg] rounded-[2.6rem] border-[5px] border-zinc-400 bg-ink p-4 shadow-2xl md:w-64">
<p class="mx-auto w-fit rounded-full bg-ink-card px-3 py-1 text-[10px] text-white/70">Tuesday Tempo Run</p>
<svg viewBox="0 0 200 180" class="mt-4 w-full"><path d="M40 70 L40 40 L110 40 L150 80 L130 100 M60 150 L60 110 L90 110 L110 150 L150 150" fill="none" stroke="#c8ee5c" stroke-width="5" stroke-linejoin="round" class="glow"/><circle cx="40" cy="70" r="7" fill="#c8ee5c"/><circle cx="150" cy="150" r="10" fill="#c8ee5c"/></svg>
<p class="mt-2 text-center font-serif text-5xl leading-none text-white">38:14</p>
<p class="text-center text-[10px] text-white/50">Duration</p>
<p class="mt-3 grid grid-cols-3 text-center font-serif text-xl text-white"><span>412</span><span>6,20</span><span>142</span></p>
<span class="mx-auto mt-4 grid h-12 w-12 place-items-center rounded-full bg-lime text-ink"><i class="fa-solid fa-play"></i></span>
</div>
<div class="relative mt-20 w-56 shrink-0 rotate-[8deg] overflow-hidden rounded-[2.6rem] border-[5px] border-zinc-400 bg-ink shadow-2xl md:w-64">
<img src="https://images.unsplash.com/photo-1530143311094-34d807799e8f?auto=format&fit=crop&w=500&h=900&q=80" alt="" class="aspect-[9/16] w-full object-cover grayscale">
<div class="absolute inset-x-0 bottom-0 bg-gradient-to-t from-ink to-transparent p-5 pt-16 text-white">
<p class="text-[10px] text-white/60">Clear · 14°C</p>
<p class="font-serif text-4xl leading-none">Morning, Ava</p>
<p class="mt-3 flex gap-1.5 text-[10px]"><span class="rounded-full bg-lime px-2 py-1 text-ink"><i class="fa-solid fa-brain"></i> Focus</span><span class="rounded-full bg-white/15 px-2 py-1"><i class="fa-solid fa-heart"></i> Calm</span></p>
</div>
</div>
</div>
</section>
<!-- block:end cta -->
<!-- block:start footer -->
<footer class="relative overflow-hidden bg-ink pt-24">
<div class="mx-auto grid max-w-4xl gap-10 px-5 sm:grid-cols-2 lg:grid-cols-[1.4fr_repeat(4,1fr)]">
<div>
<a href="index.html" class="flex items-center gap-2 text-xl font-semibold"><svg viewBox="0 0 32 32" class="h-7 w-7" aria-hidden="true"><path d="M4 18C4 10 10 4 18 4c0 8-6 14-14 14Z" fill="#c8ee5c"/><path d="M28 14c0 8-6 14-14 14 0-8 6-14 14-14Z" fill="#a9d13b"/></svg>Oruna</a>
<p class="mt-3 max-w-[12rem] text-sm text-white/50">Your AI wellness companion.</p>
</div>
<% [["Product", [["Features", "features.html"], ["Technology", "technology.html"], ["Programs", "programs.html"], ["Pricing", "pricing.html"]]], ["Explore", [["Overview", "index.html"], ["Community", "community.html"], ["Challenges", "community.html"], ["Run replay", "index.html#route"]]], ["Company", [["About", "technology.html"], ["Support", "contact.html"], ["Press", "contact.html"], ["Careers", "contact.html"]]], ["Legal", [["Terms", "contact.html"], ["Privacy", "contact.html"], ["Health data", "technology.html"], ["Cookies", "contact.html"]]]].each do |(title, links)| %>
<div>
<h2 class="text-sm text-white/40"><%= title %></h2>
<ul class="mt-4 grid gap-2 text-sm text-white/80">
<% links.each do |(label, href)| %><li><a href="<%= href %>" class="transition hover:text-lime"><%= label %></a></li><% end %>
</ul>
</div>
<% end %>
</div>
<p class="mx-auto mt-14 flex max-w-4xl flex-wrap justify-between gap-3 px-5 text-xs text-white/40"><span>© 2026 Oruna Labs. Not a medical device.</span><span class="flex gap-4"><a href="contact.html" class="hover:text-lime" aria-label="Instagram"><i class="fa-brands fa-instagram" aria-hidden="true"></i></a><a href="contact.html" class="hover:text-lime" aria-label="YouTube"><i class="fa-brands fa-youtube" aria-hidden="true"></i></a><a href="contact.html" class="hover:text-lime" aria-label="Discord"><i class="fa-brands fa-discord" aria-hidden="true"></i></a></span></p>
<div class="relative mt-10 h-56 md:h-72" aria-hidden="true">
<div class="absolute inset-x-0 bottom-0 h-40 bg-lime/70 blur-[90px]"></div>
<svg viewBox="0 0 32 32" class="absolute left-1/2 top-6 h-72 w-72 -translate-x-1/2 opacity-90 md:h-96 md:w-96"><path d="M4 18C4 10 10 4 18 4c0 8-6 14-14 14Z" fill="#c8ee5c"/><path d="M28 14c0 8-6 14-14 14 0-8 6-14 14-14Z" fill="#a9d13b"/></svg>
</div>
</footer>
<!-- block:end footer -->
<script>
const reducedMotion = matchMedia('(prefers-reduced-motion: reduce)').matches;
const watchMotion = !reducedMotion && !navigator.webdriver && 'IntersectionObserver' in window;
document.querySelectorAll('[data-menu-toggle]').forEach((button) => {
const menu = document.getElementById(button.getAttribute('aria-controls'));
button.addEventListener('click', () => {
const open = button.getAttribute('aria-expanded') === 'true';
button.setAttribute('aria-expanded', String(!open));
menu.classList.toggle('hidden', open);
});
});
document.querySelectorAll('[data-nav-pill]').forEach((pill) => {
const update = () => { pill.dataset.scrolled = String(pill.dataset.solid === 'true' || scrollY > 60); };
update();
addEventListener('scroll', update, { passive: true });
});
document.querySelectorAll('[data-coach]').forEach((root) => {
const data = JSON.parse(root.querySelector('[data-coach-data]').textContent);
const input = root.querySelector('[data-coach-input]');
const reply = root.querySelector('[data-coach-reply]');
const moods = [...root.querySelectorAll('[data-mood]')];
const say = (text) => { reply.hidden = false; reply.textContent = text; };
moods.forEach((mood) => mood.addEventListener('click', () => {
moods.forEach((other) => other.setAttribute('aria-pressed', String(other === mood)));
input.value = `I want to ${data[mood.dataset.mood].label.toLowerCase()} today.`;
say(data[mood.dataset.mood].reply);
}));
root.querySelector('[data-coach-form]').addEventListener('submit', (event) => {
event.preventDefault();
if (!input.value.trim()) { input.focus(); say('Tell me how you slept or how your body feels, and I will shape today around it.'); return; }
say(`Got it. Based on "${input.value.trim().slice(0, 60)}", I've drafted a 30-minute session and moved your hard intervals to Thursday.`);
});
});
document.querySelectorAll('[data-route]').forEach((root) => {
const routes = JSON.parse(root.querySelector('[data-route-data]').textContent);
const path = root.querySelector('[data-route-path]');
const start = root.querySelector('[data-route-start]');
const pin = root.querySelector('[data-route-pin]');
const time = root.querySelector('[data-route-time]');
const stats = Object.fromEntries([...root.querySelectorAll('[data-route-stat]')].map((el) => [el.dataset.routeStat, el]));
const tabs = [...root.querySelectorAll('[data-route-tab]')];
const seconds = (value) => value.split(':').reduce((total, part) => total * 60 + Number(part), 0);
const clock = (total) => `${String(Math.floor(total / 60)).padStart(2, '0')}:${String(Math.floor(total % 60)).padStart(2, '0')}`;
let current = tabs[0].dataset.routeTab;
let frame;
const render = (route, progress) => {
const length = path.getTotalLength();
path.style.strokeDasharray = length;
path.style.strokeDashoffset = length * (1 - progress);
const point = path.getPointAtLength(length * progress);
pin.setAttribute('transform', `translate(${point.x} ${point.y})`);
time.textContent = progress >= 1 ? route.time : clock(seconds(route.time) * progress);
stats.kcal.textContent = Math.round(route.kcal * progress);
stats.km.textContent = progress >= 1 ? route.km : (parseFloat(route.km.replace(',', '.')) * progress).toFixed(2).replace('.', ',');
stats.bpm.textContent = Math.round(90 + (route.bpm - 90) * progress);
};
const play = () => {
const route = routes[current];
path.setAttribute('d', route.d);
const first = path.getPointAtLength(0);
start.setAttribute('transform', `translate(${first.x} ${first.y})`);
cancelAnimationFrame(frame);
if (reducedMotion) { render(route, 1); return; }
const begin = performance.now();
const step = (now) => { const progress = Math.min((now - begin) / 2400, 1); render(route, progress); if (progress < 1) frame = requestAnimationFrame(step); };
frame = requestAnimationFrame(step);
};
tabs.forEach((tab) => tab.addEventListener('click', () => {
tabs.forEach((other) => other.setAttribute('aria-selected', String(other === tab)));
current = tab.dataset.routeTab;
play();
}));
root.querySelector('[data-route-play]').addEventListener('click', play);
});
document.querySelectorAll('[data-rhythm]').forEach((root) => {
const data = JSON.parse(root.querySelector('[data-rhythm-data]').textContent);
const tabs = [...root.querySelectorAll('[data-period]')];
tabs.forEach((tab) => tab.addEventListener('click', () => {
tabs.forEach((other) => other.setAttribute('aria-selected', String(other === tab)));
data[tab.dataset.period].forEach((item, index) => {
const card = root.querySelector(`[data-rhythm-card="${index}"]`);
card.querySelector('[data-rhythm-value]').textContent = item.value.toLocaleString('en-US');
card.querySelector('[data-rhythm-goal]').textContent = `/ ${item.goal}`;
const ticks = [...card.querySelector('[data-rhythm-ticks]').children];
const filled = Math.round(item.ratio * ticks.length);
ticks.forEach((tick, i) => {
tick.classList.toggle('bg-lime', i < filled - 1);
tick.classList.toggle('bg-ember', i === filled - 1);
tick.classList.toggle('shadow-[0_0_12px_#ff8a3d]', i === filled - 1);
tick.classList.toggle('bg-white/5', i >= filled);
});
});
}));
});
if (watchMotion) {
document.documentElement.classList.add('reveal-ready');
const observer = new IntersectionObserver((entries) => entries.forEach((entry) => {
if (entry.isIntersecting) { entry.target.classList.add('is-visible'); observer.unobserve(entry.target); }
}), { threshold: 0.15 });
document.querySelectorAll('[data-reveal]').forEach((el) => observer.observe(el));
}
</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