mirror of
https://github.com/ivuorinen/homebrew-tap.git
synced 2026-02-18 23:51:42 +00:00
feat: full site
This commit is contained in:
10
theme/_command_input.html.erb
Normal file
10
theme/_command_input.html.erb
Normal file
@@ -0,0 +1,10 @@
|
||||
<div class="install-cmd relative flex items-center gap-<%= size == 'sm' ? '2' : '4' %> rounded-2xl border border-slate-200 bg-slate-900 shadow-sm dark:border-slate-700 dark:bg-slate-950">
|
||||
<input
|
||||
type="text"
|
||||
readonly
|
||||
value="<%= h(command) %>"
|
||||
onclick="this.select()"
|
||||
class="flex-1 bg-transparent <%= size == 'sm' ? 'py-2 text-xs' : 'py-3 text-sm' %> font-mono text-slate-100 outline-none cursor-pointer border-0 appearance-none focus:outline-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-sky-400 dark:focus-visible:outline-sky-300 focus-visible:outline-offset-[-2px] rounded-2xl px-8"
|
||||
>
|
||||
<span class="<%= size == 'sm' ? 'text-[10px]' : 'text-xs' %> uppercase tracking-wide text-slate-400 pr-8"><%= h(label || 'CLI') %></span>
|
||||
</div>
|
||||
7
theme/_footer.html.erb
Normal file
7
theme/_footer.html.erb
Normal file
@@ -0,0 +1,7 @@
|
||||
<footer class="border-t border-slate-200 pt-6 text-sm text-slate-500 dark:border-slate-700 dark:text-slate-400">
|
||||
<p class="px-8">
|
||||
<time datetime="<%= h(generated_at) %>" title="<%= h(generated_at) %>">
|
||||
Generated at <%= h(format_relative_time(generated_at)) %>
|
||||
</time>
|
||||
</p>
|
||||
</footer>
|
||||
55
theme/_formula_card.html.erb
Normal file
55
theme/_formula_card.html.erb
Normal file
@@ -0,0 +1,55 @@
|
||||
<%
|
||||
# Required parameters:
|
||||
# - formula: The formula hash object
|
||||
# - base_path: Path prefix for formula links (e.g., '' or '../')
|
||||
# Optional parameters:
|
||||
# - show_homepage: Show homepage link (default: false)
|
||||
# - show_dependencies: Show dependency badges (default: false)
|
||||
# - show_last_modified: Show last modified time (default: false)
|
||||
|
||||
formula_name = formula['name'].to_s
|
||||
return if formula_name.empty?
|
||||
formula_slug = formula_name.gsub(/[^a-z0-9._-]/i, '-')
|
||||
formula_slug = formula_name if formula_slug.empty?
|
||||
formula_description = formula['description'].to_s
|
||||
# Check if methods exist (they're defined as singleton methods in PartialContext)
|
||||
show_homepage = respond_to?(:show_homepage) ? self.show_homepage : false
|
||||
show_dependencies = respond_to?(:show_dependencies) ? self.show_dependencies : false
|
||||
show_last_modified = respond_to?(:show_last_modified) ? self.show_last_modified : false
|
||||
%>
|
||||
<article class="formula-card flex h-full flex-col gap-4 rounded-3xl border border-slate-200 bg-white/80 p-6 shadow-sm ring-1 ring-black/5 transition hover:-translate-y-1 hover:shadow-md dark:border-slate-700 dark:bg-slate-900/70 dark:ring-white/10">
|
||||
<header class="space-y-2">
|
||||
<h3 class="text-xl font-semibold">
|
||||
<a href="<%= base_path %>formula/<%= h(formula_slug) %>.html" class="transition hover:text-sky-600 dark:hover:text-sky-400"><%= h(formula_name) %></a>
|
||||
</h3>
|
||||
<p class="text-sm text-slate-600 dark:text-slate-300"><%= h(formula_description) %></p>
|
||||
<% if show_homepage && formula['homepage'] %>
|
||||
<p class="text-sm">
|
||||
<a href="<%= h(formula['homepage'].to_s) %>" target="_blank" class="inline-flex items-center gap-1 text-sky-600 transition hover:text-sky-500 dark:text-sky-400 dark:hover:text-sky-300">
|
||||
Homepage
|
||||
<span aria-hidden="true">↗</span>
|
||||
</a>
|
||||
</p>
|
||||
<% end %>
|
||||
</header>
|
||||
|
||||
<% if show_last_modified && formula['last_modified'] %>
|
||||
<div class="flex items-center gap-2 text-xs text-slate-500 dark:text-slate-400">
|
||||
<span>Updated <%= format_relative_time(formula['last_modified']) %></span>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<% if formula['license'] %>
|
||||
<span class="badge inline-flex items-center rounded-full bg-sky-500 px-3 py-1 text-xs font-semibold uppercase tracking-wide text-white dark:bg-sky-400">License: <%= h(formula['license'].to_s) %></span>
|
||||
<% end %>
|
||||
<% if formula['version'] %>
|
||||
<span class="badge inline-flex items-center rounded-full bg-emerald-500 px-3 py-1 text-xs font-semibold uppercase tracking-wide text-white dark:bg-emerald-400">v<%= h(formula['version'].to_s) %></span>
|
||||
<% end %>
|
||||
<% if show_dependencies %>
|
||||
<% Array(formula['dependencies']).compact.each do |dep| %>
|
||||
<span class="badge inline-flex items-center rounded-full bg-slate-800 px-3 py-1 text-xs uppercase tracking-wide text-white dark:bg-slate-700">dep: <%= h(dep.to_s) %></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</article>
|
||||
31
theme/_head.html.erb
Normal file
31
theme/_head.html.erb
Normal file
@@ -0,0 +1,31 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<% page_title = title.to_s %>
|
||||
<title><%= h(page_title) %></title>
|
||||
<% stylesheet = style_path || 'style.css' %>
|
||||
<link rel="stylesheet" href="<%= h(stylesheet) %>">
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,typography"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
darkMode: "class"
|
||||
};
|
||||
</script>
|
||||
<script>
|
||||
(function () {
|
||||
if (typeof window === "undefined") return;
|
||||
try {
|
||||
const stored = window.localStorage.getItem("theme");
|
||||
const prefersDark = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
const theme = stored || (prefersDark ? "dark" : "light");
|
||||
document.documentElement.classList.toggle("dark", theme === "dark");
|
||||
document.documentElement.classList.toggle("light", theme !== "dark");
|
||||
document.documentElement.setAttribute("data-theme", theme);
|
||||
} catch (error) {
|
||||
document.documentElement.classList.toggle("dark", false);
|
||||
document.documentElement.classList.toggle("light", true);
|
||||
document.documentElement.setAttribute("data-theme", "light");
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
15
theme/_header.html.erb
Normal file
15
theme/_header.html.erb
Normal file
@@ -0,0 +1,15 @@
|
||||
<header class="flex flex-col gap-6">
|
||||
<nav aria-label="Main navigation" class="px-8">
|
||||
<%= render_partial('nav', active: active_page, base_path: base_path) %>
|
||||
</nav>
|
||||
|
||||
<div class="header flex flex-col gap-6 rounded-3xl border border-slate-200 bg-white/80 p-8 shadow-sm ring-1 ring-black/5 backdrop-blur dark:border-slate-700 dark:bg-slate-900/80 dark:ring-white/10">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div class="space-y-2">
|
||||
<h1 class="text-3xl font-bold tracking-tight md:text-4xl"><%= h(tap_name) %></h1>
|
||||
<p class="max-w-3xl text-base text-slate-600 dark:text-slate-300"><%= h(description) %></p>
|
||||
</div>
|
||||
<button class="theme-toggle inline-flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full border border-slate-300 bg-white/80 text-xl text-slate-600 shadow-sm transition hover:bg-slate-100 hover:text-slate-900 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-500 dark:border-slate-600 dark:bg-slate-900/70 dark:text-slate-300 dark:hover:bg-slate-800 dark:hover:text-white" aria-label="Toggle dark mode" tabindex="0">🌙</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
13
theme/_nav.html.erb
Normal file
13
theme/_nav.html.erb
Normal file
@@ -0,0 +1,13 @@
|
||||
<% nav_base = "rounded-full px-3 py-1 transition hover:bg-slate-100 hover:text-slate-900 dark:hover:bg-slate-800 dark:hover:text-white" %>
|
||||
<% nav_active = "bg-slate-900 text-white dark:bg-slate-100 dark:text-slate-900" %>
|
||||
<% active_tab = (active || :none).to_sym rescue :none %>
|
||||
<% prefix = base_path.to_s %>
|
||||
<% href = lambda do |page|
|
||||
prefix.empty? ? page : File.join(prefix, page)
|
||||
end %>
|
||||
<nav aria-label="Main navigation" class="flex flex-wrap items-center gap-3 text-sm font-medium text-slate-600 dark:text-slate-300">
|
||||
<% home_classes = [nav_base, (active_tab == :home ? nav_active : nil)].compact.join(' ') %>
|
||||
<% formulae_classes = [nav_base, (active_tab == :formulae ? nav_active : nil)].compact.join(' ') %>
|
||||
<a href="<%= h(href.call('index.html')) %>" class="<%= home_classes %>">Home</a>
|
||||
<a href="<%= h(href.call('formulae.html')) %>" class="<%= formulae_classes %>">All Formulae</a>
|
||||
</nav>
|
||||
6
theme/_nothing_here.html.erb
Normal file
6
theme/_nothing_here.html.erb
Normal file
@@ -0,0 +1,6 @@
|
||||
<main class="flex flex-1 flex-col gap-8">
|
||||
<div class="rounded-2xl border border-slate-200 bg-white/80 p-8 text-center shadow-sm ring-1 ring-black/5 backdrop-blur dark:border-slate-700 dark:bg-slate-900/80 dark:ring-white/10">
|
||||
<h2 class="text-2xl font-semibold tracking-tight mb-4">No formulae available</h2>
|
||||
<p class="text-slate-600 dark:text-slate-300">This tap currently has no formulae. Please check back later.</p>
|
||||
</div>
|
||||
</main>
|
||||
BIN
theme/assets/MonaspaceArgonVar.woff
Normal file
BIN
theme/assets/MonaspaceArgonVar.woff
Normal file
Binary file not shown.
BIN
theme/assets/MonaspaceArgonVar.woff2
Normal file
BIN
theme/assets/MonaspaceArgonVar.woff2
Normal file
Binary file not shown.
111
theme/formula.html.erb
Normal file
111
theme/formula.html.erb
Normal file
@@ -0,0 +1,111 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="h-full min-h-full scroll-smooth font-sans transition-colors duration-200">
|
||||
<% raw_tap_name = @data['tap_name'].to_s %>
|
||||
<% tap_name = raw_tap_name.empty? ? 'ivuorinen/tap' : raw_tap_name %>
|
||||
<% formula_name = @formula['name'].to_s %>
|
||||
<% name = h("#{tap_name}/#{formula_name}") %>
|
||||
<%= render_partial('head', title: "#{formula_name} - #{tap_name}", style_path: '../style.css') %>
|
||||
<body class="min-h-screen bg-slate-50 text-slate-900 transition-colors duration-200 dark:bg-slate-950 dark:text-slate-100">
|
||||
<div class="mx-auto flex min-h-screen max-w-6xl flex-col gap-12 px-6 py-10">
|
||||
<%= render_partial('header', tap_name: name, description: @formula['description'].to_s, active_page: :formulae, base_path: '../') %>
|
||||
|
||||
<main class="flex flex-1 flex-col gap-8">
|
||||
<section class="space-y-4">
|
||||
<div class="flex items-center justify-between px-8 pr-6">
|
||||
<h2 class="text-2xl font-semibold tracking-tight h-10">Installation</h2>
|
||||
<div class="flex gap-2">
|
||||
<% if @formula['url'] %>
|
||||
<a href="<%= h(@formula['url'].to_s) %>" target="_blank" class="inline-flex items-center justify-center rounded-full border border-slate-200 p-2 text-slate-600 transition hover:border-slate-300 hover:bg-slate-50 hover:text-slate-900 dark:border-slate-700 dark:text-slate-400 dark:hover:border-slate-600 dark:hover:bg-slate-800 dark:hover:text-white" aria-label="Download source">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
||||
<polyline points="7 10 12 15 17 10"></polyline>
|
||||
<line x1="12" y1="15" x2="12" y2="3"></line>
|
||||
</svg>
|
||||
</a>
|
||||
<% end %>
|
||||
<% if @formula['homepage'] %>
|
||||
<a href="<%= h(@formula['homepage'].to_s) %>" target="_blank" class="inline-flex items-center justify-center rounded-full border border-slate-200 p-2 text-slate-600 transition hover:border-slate-300 hover:bg-slate-50 hover:text-slate-900 dark:border-slate-700 dark:text-slate-400 dark:hover:border-slate-600 dark:hover:bg-slate-800 dark:hover:text-white" aria-label="View on GitHub">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
||||
<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>
|
||||
</a>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<%= render_partial('command_input', command: "brew install #{name}", size: 'lg', label: 'CLI') %>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="overflow-hidden rounded-2xl border border-slate-200 bg-white/80 shadow-sm ring-1 ring-black/5 dark:border-slate-700 dark:bg-slate-900/70 dark:ring-white/10">
|
||||
<table class="w-full divide-y divide-slate-200 text-sm dark:divide-slate-700">
|
||||
<caption class="sr-only">Formula Details</caption>
|
||||
<tbody>
|
||||
<% if @formula['version'] %>
|
||||
<tr class="divide-x divide-slate-200 dark:divide-slate-700">
|
||||
<th scope="row" class="w-40 bg-slate-50 px-4 py-3 text-left font-medium text-slate-700 dark:bg-slate-900 dark:text-slate-300">Version</th>
|
||||
<td class="px-4 py-3 text-slate-600 dark:text-slate-200"><%= h(@formula['version'].to_s) %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% if @formula['license'] %>
|
||||
<tr class="divide-x divide-slate-200 dark:divide-slate-700">
|
||||
<th scope="row" class="w-40 bg-slate-50 px-4 py-3 text-left font-medium text-slate-700 dark:bg-slate-900 dark:text-slate-300">License</th>
|
||||
<td class="px-4 py-3 text-slate-600 dark:text-slate-200"><%= h(@formula['license'].to_s) %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% if @formula['homepage'] %>
|
||||
<tr class="divide-x divide-slate-200 dark:divide-slate-700">
|
||||
<th scope="row" class="w-40 bg-slate-50 px-4 py-3 text-left font-medium text-slate-700 dark:bg-slate-900 dark:text-slate-300">Homepage</th>
|
||||
<td class="px-4 py-3">
|
||||
<a href="<%= h(@formula['homepage'].to_s) %>" target="_blank" class="inline-flex items-center gap-1 text-sky-600 transition hover:text-sky-500 dark:text-sky-400 dark:hover:text-sky-300">
|
||||
<%= h(@formula['homepage'].to_s) %>
|
||||
<span aria-hidden="true">↗</span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% dependencies = Array(@formula['dependencies']).reject(&:nil?) %>
|
||||
<% if dependencies.any? %>
|
||||
<tr class="divide-x divide-slate-200 dark:divide-slate-700">
|
||||
<th scope="row" class="w-40 bg-slate-50 px-4 py-3 text-left font-medium text-slate-700 dark:bg-slate-900 dark:text-slate-300">Dependencies</th>
|
||||
<td class="px-4 py-3">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<% dependencies.each do |dep| %>
|
||||
<span class="inline-flex items-center rounded-full bg-slate-800 px-3 py-1 text-xs font-semibold uppercase tracking-wide text-white dark:bg-slate-700">dep: <%= h(dep.to_s) %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<tr class="divide-x divide-slate-200 dark:divide-slate-700">
|
||||
<th scope="row" class="w-40 bg-slate-50 px-4 py-3 text-left font-medium text-slate-700 dark:bg-slate-900 dark:text-slate-300">Formula File</th>
|
||||
<td class="px-4 py-3"><code class="rounded-xl bg-slate-900 px-3 py-2 font-mono text-xs text-slate-100 dark:bg-slate-950"><%= h(@formula['file_path'].to_s) %></code></td>
|
||||
</tr>
|
||||
<tr class="divide-x divide-slate-200 dark:divide-slate-700">
|
||||
<th scope="row" class="w-40 bg-slate-50 px-4 py-3 text-left font-medium text-slate-700 dark:bg-slate-900 dark:text-slate-300">Last Modified</th>
|
||||
<td class="px-4 py-3 text-slate-600 dark:text-slate-200"><%= h(@formula['last_modified'].to_s) %></td>
|
||||
</tr>
|
||||
<% if @formula['sha256'] %>
|
||||
<tr class="divide-x divide-slate-200 dark:divide-slate-700">
|
||||
<th scope="row" class="w-40 bg-slate-50 px-4 py-3 text-left font-medium text-slate-700 dark:bg-slate-900 dark:text-slate-300">SHA256</th>
|
||||
<td class="px-4 py-3">
|
||||
<input
|
||||
type="text"
|
||||
readonly
|
||||
value="<%= h(@formula['sha256'].to_s) %>"
|
||||
onclick="this.select()"
|
||||
class="w-full bg-slate-50 dark:bg-slate-800 px-3 py-1.5 font-mono text-xs text-slate-800 dark:text-slate-200 rounded-lg border border-slate-200 dark:border-slate-600 cursor-pointer appearance-none focus:outline-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-sky-400 dark:focus-visible:outline-sky-300 focus-visible:outline-offset-[-2px]"
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<%= render_partial('footer', generated_at: @data['generated_at']) %>
|
||||
<script src="../main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
46
theme/formulae.html.erb
Normal file
46
theme/formulae.html.erb
Normal file
@@ -0,0 +1,46 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="h-full min-h-full scroll-smooth font-sans transition-colors duration-200">
|
||||
<% raw_tap_name = @data['tap_name'].to_s %>
|
||||
<% tap_name = raw_tap_name.empty? ? 'ivuorinen/tap' : raw_tap_name %>
|
||||
<%= render_partial('head', title: "All Formulae - #{tap_name}", style_path: 'style.css') %>
|
||||
<body class="min-h-screen bg-slate-50 text-slate-900 transition-colors duration-200 dark:bg-slate-950 dark:text-slate-100">
|
||||
<div class="mx-auto flex min-h-screen max-w-6xl flex-col gap-12 px-6 py-10">
|
||||
<%= render_partial('header', tap_name: tap_name, description: 'Homebrew Tap containing custom formulae for various tools and utilities.', active_page: :formulae, base_path: '') %>
|
||||
|
||||
<% if @data['formulae_count'].to_i == 0 %>
|
||||
<%= render_partial('nothing_here') %>
|
||||
<% else %>
|
||||
|
||||
<main class="flex flex-1 flex-col gap-8">
|
||||
<section class="space-y-6">
|
||||
<div class="flex items-end justify-between gap-4">
|
||||
<h2 class="text-2xl font-semibold tracking-tight px-8">All Formulae</h2>
|
||||
<p class="text-sm text-slate-500 dark:text-slate-400 px-8"><%= h(@data['formulae_count'].to_s) %> total</p>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<input
|
||||
type="search"
|
||||
id="formula-search"
|
||||
placeholder="Search formulae..."
|
||||
class="w-full rounded-xl border border-slate-200 bg-white px-8 py-2 text-sm placeholder-slate-400 shadow-sm transition focus:border-sky-400 focus:outline-none focus:ring-1 focus:ring-sky-400 dark:border-slate-700 dark:bg-slate-900 dark:placeholder-slate-500 dark:focus:border-sky-300 dark:focus:ring-sky-300"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="formula-grid grid gap-6 md:grid-cols-2 xl:grid-cols-3">
|
||||
<%
|
||||
# Sort all formulae alphabetically by name
|
||||
@data['formulae'].sort_by { |f| f['name'].to_s.downcase }.each do |formula|
|
||||
%>
|
||||
<%= render_partial('formula_card', formula: formula, base_path: '', show_homepage: true, show_dependencies: true, show_last_modified: true) %>
|
||||
<% end %>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<% end %>
|
||||
|
||||
<%= render_partial('footer', generated_at: @data['generated_at']) %>
|
||||
</div>
|
||||
<script src="main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
49
theme/index.html.erb
Normal file
49
theme/index.html.erb
Normal file
@@ -0,0 +1,49 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="h-full min-h-full scroll-smooth font-sans transition-colors duration-200">
|
||||
<% raw_tap_name = @data['tap_name'].to_s %>
|
||||
<% safe_tap_name = raw_tap_name.empty? ? 'ivuorinen/homebrew-tap' : raw_tap_name %>
|
||||
<%= render_partial('head', title: safe_tap_name, style_path: 'style.css') %>
|
||||
<body class="min-h-screen bg-slate-50 text-slate-900 transition-colors duration-200 dark:bg-slate-950 dark:text-slate-100">
|
||||
<div class="mx-auto flex min-h-screen max-w-6xl flex-col gap-12 px-6 py-10">
|
||||
<%= render_partial('header', tap_name: safe_tap_name, description: 'Homebrew Tap containing custom formulae for various tools and utilities.', active_page: :home, base_path: '') %>
|
||||
|
||||
<% if @data['formulae_count'].to_i == 0 %>
|
||||
<%= render_partial('nothing_here') %>
|
||||
<% else %>
|
||||
|
||||
<main class="flex flex-1 flex-col gap-8">
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-2xl font-semibold tracking-tight px-8 h-10">Quick Start</h2>
|
||||
<%= render_partial('command_input', command: "brew tap #{safe_tap_name}", size: 'lg', label: 'CLI') %>
|
||||
</section>
|
||||
|
||||
<section class="space-y-6">
|
||||
<div class="flex items-end justify-between gap-4">
|
||||
<h2 class="text-2xl font-semibold tracking-tight px-8">Latest Updates</h2>
|
||||
<a href="formulae.html" class="text-sm text-sky-600 hover:text-sky-500 dark:text-sky-400 dark:hover:text-sky-300 px-8">View all <%= h(@data['formulae_count'].to_s) %> formulae →</a>
|
||||
</div>
|
||||
|
||||
<div class="formula-grid grid gap-6 md:grid-cols-2 xl:grid-cols-3">
|
||||
<%
|
||||
# Sort formulae by last_modified date (most recent first) and take top 6
|
||||
sorted_formulae = Array(@data['formulae'])
|
||||
.select { |f| f.is_a?(Hash) && f['last_modified'] }
|
||||
.sort_by { |f| f['last_modified'] }
|
||||
.reverse
|
||||
.first(6)
|
||||
|
||||
sorted_formulae.each do |formula|
|
||||
%>
|
||||
<%= render_partial('formula_card', formula: formula, base_path: '', show_last_modified: true) %>
|
||||
<% end %>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<% end %>
|
||||
|
||||
|
||||
<%= render_partial('footer', generated_at: @data['generated_at'].to_s) %>
|
||||
</div>
|
||||
<script src="main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
156
theme/main.js
Normal file
156
theme/main.js
Normal file
@@ -0,0 +1,156 @@
|
||||
// Dark mode toggle functionality
|
||||
(() => {
|
||||
const STORAGE_KEY = "theme";
|
||||
|
||||
function getStoredTheme() {
|
||||
try {
|
||||
return localStorage.getItem(STORAGE_KEY);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function setStoredTheme(theme) {
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, theme);
|
||||
} catch {
|
||||
// Ignore storage failures
|
||||
}
|
||||
}
|
||||
|
||||
function getSystemTheme() {
|
||||
return window.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
||||
}
|
||||
|
||||
function getCurrentTheme() {
|
||||
return getStoredTheme() || getSystemTheme() || "light";
|
||||
}
|
||||
|
||||
function applyTheme(theme) {
|
||||
document.documentElement.classList.toggle("dark", theme === "dark");
|
||||
const toggle = document.querySelector(".theme-toggle");
|
||||
if (toggle) {
|
||||
toggle.innerHTML = theme === "dark" ? "☀️" : "🌙";
|
||||
toggle.setAttribute(
|
||||
"aria-label",
|
||||
theme === "dark" ? "Switch to light mode" : "Switch to dark mode",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
const currentTheme = getCurrentTheme();
|
||||
const newTheme = currentTheme === "dark" ? "light" : "dark";
|
||||
setStoredTheme(newTheme);
|
||||
applyTheme(newTheme);
|
||||
}
|
||||
|
||||
// Watch for system theme changes
|
||||
const mediaQuery = window.matchMedia?.("(prefers-color-scheme: dark)");
|
||||
mediaQuery?.addEventListener("change", (e) => {
|
||||
if (!getStoredTheme()) {
|
||||
applyTheme(e.matches ? "dark" : "light");
|
||||
}
|
||||
});
|
||||
|
||||
// Theme toggle click handler
|
||||
document.addEventListener("click", (e) => {
|
||||
if (e.target.closest(".theme-toggle")) {
|
||||
e.preventDefault();
|
||||
toggleTheme();
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize theme
|
||||
applyTheme(getCurrentTheme());
|
||||
window.toggleTheme = toggleTheme;
|
||||
})();
|
||||
|
||||
// Click-to-copy functionality for command inputs
|
||||
(() => {
|
||||
async function copyToClipboard(input) {
|
||||
input.select();
|
||||
input.setSelectionRange(0, 99999);
|
||||
|
||||
try {
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
await navigator.clipboard.writeText(input.value);
|
||||
return true;
|
||||
}
|
||||
} catch {
|
||||
// Keep text selected for manual copy
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function showCopyFeedback(element, success) {
|
||||
if (!success) return;
|
||||
|
||||
const label = element.closest(".install-cmd")?.querySelector("span");
|
||||
if (!label) return;
|
||||
|
||||
const originalText = label.textContent;
|
||||
label.textContent = "Copied!";
|
||||
label.style.color = "#10b981";
|
||||
|
||||
setTimeout(() => {
|
||||
label.textContent = originalText;
|
||||
label.style.color = "";
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
// Setup copy handlers using event delegation
|
||||
document.addEventListener("click", async (e) => {
|
||||
const input = e.target.closest('input[readonly][onclick*="select"]');
|
||||
if (input) {
|
||||
input.removeAttribute("onclick");
|
||||
const success = await copyToClipboard(input);
|
||||
showCopyFeedback(input, success);
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
// Formula search functionality
|
||||
(() => {
|
||||
let searchTimeout = null;
|
||||
const searchInput = document.getElementById("formula-search");
|
||||
if (!searchInput) return;
|
||||
|
||||
function fuzzyMatch(needle, haystack) {
|
||||
if (!needle) return true;
|
||||
|
||||
needle = needle.toLowerCase();
|
||||
haystack = haystack.toLowerCase();
|
||||
|
||||
let j = 0;
|
||||
for (let i = 0; i < needle.length; i++) {
|
||||
const char = needle[i];
|
||||
j = haystack.indexOf(char, j);
|
||||
if (j === -1) return false;
|
||||
j++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function performSearch() {
|
||||
const searchTerm = searchInput.value.trim();
|
||||
const cards = document.querySelectorAll(".formula-card");
|
||||
|
||||
cards.forEach((card) => {
|
||||
const text = `${card.querySelector("h3")?.textContent || ""} ${card.querySelector("p")?.textContent || ""}`;
|
||||
card.style.display = !searchTerm || fuzzyMatch(searchTerm, text) ? "" : "none";
|
||||
});
|
||||
}
|
||||
searchInput.addEventListener("input", () => {
|
||||
clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(performSearch, 300);
|
||||
});
|
||||
|
||||
searchInput.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Escape") {
|
||||
e.preventDefault();
|
||||
searchInput.value = "";
|
||||
performSearch();
|
||||
}
|
||||
});
|
||||
})();
|
||||
43
theme/style.css
Normal file
43
theme/style.css
Normal file
@@ -0,0 +1,43 @@
|
||||
@font-face {
|
||||
font-family: "Monaspace Argon";
|
||||
src:
|
||||
url("assets/MonaspaceArgonVar.woff2") format("woff2"),
|
||||
url("assets/MonaspaceArgonVar.woff") format("woff");
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family:
|
||||
"Monaspace Argon",
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
"Segoe UI",
|
||||
Helvetica,
|
||||
Arial,
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family:
|
||||
"Monaspace Argon",
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
"Segoe UI",
|
||||
Helvetica,
|
||||
Arial,
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
code,
|
||||
pre {
|
||||
font-family: "Monaspace Argon", monospace;
|
||||
font-feature-settings:
|
||||
"calt", "ss01", "ss02", "ss03", "ss04", "ss05", "ss06", "ss07", "ss08", "ss09", "ss10",
|
||||
"liga";
|
||||
}
|
||||
Reference in New Issue
Block a user