Free
Footer
Velvoro Footer
A footer block extracted from the Velvoro template, available in Tailwind CSS and Bootstrap.
0
3
A footer block extracted from the Velvoro template, available in Tailwind CSS and Bootstrap.
<!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>Velvoro Footer | Tailwind CSS UI Block</title>
<meta name="description" content="A footer block extracted from the Velvoro template, available in Tailwind CSS and Bootstrap.">
<meta name="keywords" content="Velvoro, Tailwind CSS template, HTML website template, portfolio template, fashion photography portfolio, editorial magazine template, photo archive, lookbook website, creative studio">
<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="Velvoro | Tailwind CSS Portfolio Template">
<meta property="og:description" content="An editorial Tailwind CSS portfolio template for fashion photographers, with a split collage hero, a look switcher and a contact sheet you can mark up.">
<meta property="og:image" content="">
<meta property="og:image:alt" content="Velvoro template preview">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:url" content="">
<meta name="twitter:title" content="Velvoro | Tailwind CSS Portfolio Template">
<meta name="twitter:description" content="An editorial Tailwind CSS portfolio template for fashion photographers, with a split collage hero, a look switcher and a contact sheet you can mark up.">
<meta name="twitter:image" content="">
<meta name="twitter:image:alt" content="Velvoro 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&family=Jost:wght@200;300;400;500&display=swap" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
colors: { olive: { DEFAULT: '#6f7550', deep: '#4e5338' }, rust: '#b8382c', cream: '#efe7dd', ink: '#1c1f1d', slate: '#2d3430' },
fontFamily: { sans: ['Inter', 'sans-serif'], wide: ['Jost', '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; }
.gridlines { background-image: linear-gradient(to right, rgba(255,255,255,.22) 1px, transparent 1px), linear-gradient(to bottom, rgba(255,255,255,.22) 1px, transparent 1px); background-size: 33.333% 33.333%; }
.spaced { letter-spacing: .9em; margin-right: -.9em; }
.swap-img { transition: opacity .5s ease; }
.pick-ring { stroke-dasharray: 100; stroke-dashoffset: 101; opacity: 0; transition: stroke-dashoffset .45s ease, opacity .2s; }
[aria-pressed="true"] .pick-ring { stroke-dashoffset: 0; opacity: 1; }
.menu-overlay { transition: opacity .4s ease, visibility .4s; }
.menu-overlay:not(.is-open) { opacity: 0; visibility: hidden; }
.reveal-ready [data-reveal] { opacity: 0; transform: translateY(24px); transition: opacity .7s ease, transform .7s 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; }
.swap-img, .pick-ring, .menu-overlay { transition: none; }
::view-transition-old(root), ::view-transition-new(root) { animation: none; }
}
</style>
</head>
<body class="bg-slate font-sans font-light text-cream antialiased">
<% active_page = "index" %>
<% nav = { "index" => "Home", "archive" => "Archive", "the-red-room" => "Stories", "about" => "About", "contact" => "Contact" } %>
<% nav.each_with_index do |(slug, label), index| %>
<% active = active_page == slug %>
<% end %>
<% houses = [["Maison Orrel", "1531746020798-e6953c6e8e04", "Woman with a nose ring against a dusty pink wall"], ["Castellane", "1506863530036-1efeddceb993", "Black and white portrait with freckles and short curls"], ["Ivo Brask", "1502823403499-6ccfcf4fb453", "Profile of a woman bathed in violet light"], ["Atelier Senne", "1521572267360-ee0c2909d518", "Bearded man in dramatic low light"]] %>
<% houses.each_with_index do |(name, photo, alt), index| %>
<% end %>
<% houses.each_with_index do |(name, _photo, _alt), index| %>
<% end %>
<% looks = [["01", "Rust season", "1529626455594-4ff0802cfb7e", "Red-haired model in a close portrait", "1531746020798-e6953c6e8e04", "Model resting between takes against a pink wall"], ["02", "Night study", "1524504388940-b1c1722653e1", "Model in sheer black against a dark backdrop", "1506863530036-1efeddceb993", "Black and white portrait backstage"], ["03", "Violet hour", "1502823403499-6ccfcf4fb453", "Profile of a model in violet light", "1492106087820-71f1a00d2b11", "Model with lilac hair in soft daylight"], ["04", "Low light", "1521572267360-ee0c2909d518", "Bearded model in dramatic shadow", "1488161628813-04466f872be2", "Model sitting below a round window"]] %>
<% [["runway", "Runway"], ["backstage", "Backstage"]].each_with_index do |(value, label), index| %>
<% end %>
<% looks.each_with_index do |(number, title, runway, runway_alt, backstage, backstage_alt), index| %>
<% end %>
<% frames = ["1529626455594-4ff0802cfb7e", "1524504388940-b1c1722653e1", "1531746020798-e6953c6e8e04", "1506863530036-1efeddceb993", "1502823403499-6ccfcf4fb453", "1503185912284-5271ff81b9a8", "1521572267360-ee0c2909d518", "1492106087820-71f1a00d2b11", "1509631179647-0177331693ae", "1488161628813-04466f872be2", "1544005313-94ddf0286df2", "1515886657613-9f3515b0c78f"] %>
<% frames.each_with_index do |photo, index| %>
<% end %>
<% shows = [["14–28 Feb 2027", "Milan", "The Second Take", "Outtakes from twenty years of spring collections, printed large for the first time.", "1524504388940-b1c1722653e1"], ["3–19 Apr 2027", "Copenhagen", "Red Room", "One photographer, one colour, eighty portraits.", "1503185912284-5271ff81b9a8"], ["9–30 Jun 2027", "Lisbon", "Between Frames", "Contact sheets, notes and the pictures nobody chose.", "1506863530036-1efeddceb993"]] %>
<% shows.each_with_index do |(dates, city, title, text, photo), index| %>
<% end %>
<!-- block:start footer -->
<footer class="bg-slate">
<div class="mx-auto grid max-w-7xl gap-10 px-5 py-16 md:grid-cols-4 md:px-10">
<div>
<a href="index.html" class="border border-cream px-2 py-1 font-wide text-[11px] font-medium uppercase tracking-[0.3em]">Velvoro</a>
<p class="mt-6 max-w-xs text-sm leading-relaxed text-cream/60">A fashion photography archive, publishing stories from the edit since 2011.</p>
</div>
<div>
<p class="font-wide text-[11px] uppercase tracking-[0.3em] text-cream/50">Explore</p>
<ul class="mt-4 grid gap-2 text-sm">
<% nav.except("index").each do |slug, label| %><li><a href="<%= slug %>.html" class="text-cream/80 transition hover:text-rust"><%= label %></a></li><% end %>
</ul>
</div>
<div>
<p class="font-wide text-[11px] uppercase tracking-[0.3em] text-cream/50">Studio</p>
<p class="mt-4 text-sm leading-relaxed text-cream/80">Via Tortona 18<br>20144 Milan<br><a href="mailto:archive@velvoro.studio" class="underline">archive@velvoro.studio</a></p>
</div>
<div>
<p class="font-wide text-[11px] uppercase tracking-[0.3em] text-cream/50">Follow</p>
<ul class="mt-4 flex gap-4">
<% [["fa-instagram", "Instagram", "https://www.instagram.com"], ["fa-pinterest-p", "Pinterest", "https://www.pinterest.com"], ["fa-vimeo-v", "Vimeo", "https://vimeo.com"]].each do |icon, label, url| %>
<li><a href="<%= url %>" aria-label="<%= label %>" class="grid h-10 w-10 place-items-center rounded-full border border-cream/30 transition hover:border-rust hover:text-rust"><i class="fa-brands <%= icon %>" aria-hidden="true"></i></a></li>
<% end %>
</ul>
</div>
</div>
<p class="border-t border-cream/10 px-5 py-6 text-center font-wide text-[10px] uppercase tracking-[0.3em] text-cream/40 md:px-10">© <span data-current-year>2026</span> Velvoro Archive</p>
</footer>
<!-- block:end footer -->
<script>
document.querySelectorAll('[data-overlay-toggle]').forEach((button) => {
const overlay = document.getElementById(button.getAttribute('aria-controls'));
const label = button.querySelector('[data-overlay-label]');
const set = (open) => {
button.setAttribute('aria-expanded', String(open));
overlay.classList.toggle('is-open', open);
if (label) label.textContent = open ? 'Close' : 'Menu';
button.querySelector('svg, i')?.classList.toggle('rotate-45', open);
document.body.style.overflow = open ? 'hidden' : '';
};
button.addEventListener('click', () => set(button.getAttribute('aria-expanded') !== 'true'));
document.addEventListener('keydown', (event) => { if (event.key === 'Escape') set(false); });
});
document.querySelectorAll('[data-house-swap]').forEach((root) => {
const images = [...root.querySelectorAll('[data-house-image]')];
const links = [...root.querySelectorAll('[data-house]')];
const show = (index) => {
images.forEach((image) => {
const on = image.dataset.houseImage === index;
image.classList.toggle('opacity-0', !on);
image.toggleAttribute('aria-hidden', !on);
});
links.forEach((link) => link.classList.toggle('border-cream', link.dataset.house === index));
links.forEach((link) => link.classList.toggle('text-cream', link.dataset.house === index));
};
links.forEach((link) => {
link.addEventListener('mouseenter', () => show(link.dataset.house));
link.addEventListener('focus', () => show(link.dataset.house));
});
});
document.querySelectorAll('[data-look-switch]').forEach((root) => {
root.querySelectorAll('[data-look-view]').forEach((button) => {
button.addEventListener('click', () => {
const view = button.dataset.lookView;
root.querySelectorAll('[data-look-view]').forEach((other) => other.setAttribute('aria-pressed', String(other === button)));
root.querySelectorAll('[data-look]').forEach((image) => {
const on = image.dataset.look === view;
image.classList.toggle('opacity-0', !on);
image.toggleAttribute('aria-hidden', !on);
});
});
});
});
document.querySelectorAll('[data-contact-sheet]').forEach((sheet) => {
const frames = [...sheet.querySelectorAll('[data-pick]')];
const count = sheet.querySelector('[data-pick-count]');
const update = () => { count.textContent = frames.filter((frame) => frame.getAttribute('aria-pressed') === 'true').length; };
frames.forEach((frame) => frame.addEventListener('click', () => {
frame.setAttribute('aria-pressed', String(frame.getAttribute('aria-pressed') !== 'true'));
update();
}));
sheet.querySelector('[data-pick-editors]')?.addEventListener('click', () => {
frames.forEach((frame) => frame.setAttribute('aria-pressed', frame.dataset.editor));
update();
});
sheet.querySelector('[data-pick-clear]')?.addEventListener('click', () => {
frames.forEach((frame) => frame.setAttribute('aria-pressed', 'false'));
update();
});
});
document.querySelectorAll('[data-accordion]').forEach((accordion) => {
accordion.querySelectorAll('[data-accordion-trigger]').forEach((trigger) => {
const panel = document.getElementById(trigger.getAttribute('aria-controls'));
trigger.addEventListener('click', () => {
const open = trigger.getAttribute('aria-expanded') !== 'true';
trigger.setAttribute('aria-expanded', String(open));
panel.hidden = !open;
(trigger.querySelector('svg') || trigger.querySelector('i'))?.classList.toggle('rotate-45', open);
});
});
});
document.querySelectorAll('[data-sort-table]').forEach((table) => {
const body = table.querySelector('tbody');
table.querySelectorAll('[data-sort]').forEach((button) => {
button.addEventListener('click', () => {
const key = button.dataset.sort;
const ascending = button.getAttribute('aria-sort') !== 'ascending';
table.querySelectorAll('[data-sort]').forEach((other) => other.removeAttribute('aria-sort'));
button.setAttribute('aria-sort', ascending ? 'ascending' : 'descending');
[...body.rows]
.sort((a, b) => a.dataset[key].localeCompare(b.dataset[key], undefined, { numeric: true }) * (ascending ? 1 : -1))
.forEach((row) => body.append(row));
});
});
});
document.querySelectorAll('[data-chat-form]').forEach((form) => {
const steps = [...form.querySelectorAll('[data-chat-step]')];
const log = form.querySelector('[data-chat-log]');
const status = form.querySelector('[data-form-status]');
let current = 0;
const show = (index) => steps.forEach((step, i) => { step.hidden = i !== index; });
form.addEventListener('submit', (event) => {
event.preventDefault();
const field = steps[current].querySelector('[name]');
if (!field.checkValidity() || !String(field.value || '').trim()) {
status.textContent = 'Could you answer that one first?';
field.focus();
return;
}
status.textContent = '';
const bubble = document.createElement('li');
bubble.className = 'ml-auto max-w-[80%] rounded-2xl rounded-br-sm bg-rust px-4 py-3 text-cream';
bubble.textContent = field.value;
log.append(bubble);
current += 1;
if (current < steps.length) {
const question = document.createElement('li');
question.className = 'max-w-[80%] rounded-2xl rounded-bl-sm bg-cream/10 px-4 py-3';
question.textContent = steps[current].dataset.question;
log.append(question);
show(current);
steps[current].querySelector('[name]').focus();
} else {
show(-1);
const done = document.createElement('li');
done.className = 'max-w-[80%] rounded-2xl rounded-bl-sm bg-cream/10 px-4 py-3';
done.textContent = form.dataset.success;
log.append(done);
status.textContent = form.dataset.success;
}
});
show(0);
});
document.querySelectorAll('[data-contact-form]').forEach((form) => {
form.addEventListener('submit', (event) => {
event.preventDefault();
const status = form.parentElement.querySelector('[data-form-status]');
if (!form.checkValidity()) {
status.textContent = form.dataset.error;
form.querySelector(':invalid')?.focus();
return;
}
status.textContent = form.dataset.success;
form.reset();
});
});
document.querySelectorAll('[data-current-year]').forEach((el) => { el.textContent = new Date().getFullYear(); });
(() => {
const items = document.querySelectorAll('[data-reveal]');
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>
Agency
Unlock the entire library of premium templates and blocks. Renews automatically, cancel anytime.