Skip to content

Instantly share code, notes, and snippets.

@craigerskine
Last active August 5, 2022 16:36
Show Gist options
  • Save craigerskine/7ea36f139cf818b045e5e1d2afa5ee11 to your computer and use it in GitHub Desktop.
Save craigerskine/7ea36f139cf818b045e5e1d2afa5ee11 to your computer and use it in GitHub Desktop.
Twind + Vue
<!doctype html>
<html lang="en" class="selection:(bg-gray-500 text-gray-50) motion-safe:(scroll-smooth)" hidden>
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<title></title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="https://fonts.gstatic.com" rel="preconnect" />
<link href="https://fonts.googleapis.com/css2?family=Inter:[email protected]&display=swap" rel="stylesheet" />
</head>
<body class="bg-gray-200 text-gray-700 dark:(bg-gray-800 text-gray-400)">
<div id="app" class="app min-h-screen flex(& col)" v-cloak>
<header class="py-8 px-4">
<div class="container mx-auto md:(flex items-center)">
<a href="/" class="py-2 text(pri-500 3xl) leading-none font-bold uppercase flex(& wrap) items-center justify-center gap-x-4 transform motion-safe:(transition) hover:(-rotate-3) md:(justify-start) dark:(text-pri-400)">
<b class="w-12 h-12 bg-current relative z-20 rounded-full"><b class="ring(1 current) absolute inset-0 -z-10 rounded-full animate-ping"></b></b>
<span>
{{ siteCompany }}
<small class="text-sm tracking-widest font-semibold block opacity-50" v-if="siteSlogan !== ''" v-text="siteSlogan"></small>
</span>
</a>
<nav class="py-5 md:(flex(& 1) justify-end)">
<ul class="flex(& wrap) items-center justify-center gap-5 font-bold uppercase md:(justify-end)">
<li v-for="(item, index) in nav">
<a :href="item.href" class="group py-1 block relative motion-safe:(transition) hover:(text-pri-500 dark:(text-white))">
{{ item.name }}
<b class="h-[2px] w-full text-pri-500 flex justify-start absolute inset-x-0 bottom-0 dark:(text-pri-400)"><b class="w-0 bg-current motion-safe:(transition-all) group-hover:(w-full)"></b></b>
</a>
</li>
</ul>
</nav>
</div>
</header>
<main>
<section class="mb-4 py-4 px-4 bg-gradient-to-b from-gray-300 md:(mb-16 py-16) dark:(from-gray-900)">
<h1 class="mx-auto max-w-prose text-gray-600 first-line:(text-gray-900 font-black) font-semibold leading-tight text-3xl md:(w-3/4 max-w-none text-[calc(3vw+3vh+.5vmin)] tracking-tighter) dark:(text-gray-500 first-line:(text-gray-50))">
Welcome to my amazing site! Dynamic text size is still cool or something, right?...
</h1>
</section>
<section class="mx-auto px-4 max-w-prose">
<article class="prose prose-pri">
<h2>Typography</h2>
<p>Paragraph <strong>Lorem</strong> ipsum, <em>dolor</em>, sit amet <a href="#">consectetur</a> adipisicing elit. Et atque necessitatibus blanditiis eos saepe officia explicabo vel tempore quisquam reprehenderit omnis vero inventore, iusto, nihil dicta perferendis sed.</p>
<hr />
<blockquote>
<p>Blockquote looks like this. Lorem ipsum dolor, sit amet consectetur adipisicing elit. Enim, hic laborum labore quibusdam nostrum esse.</p>
</blockquote>
<div class="md:(grid(& cols-2) gap-5)">
<ul>
<li>Unordered List</li>
<li>Unordered List</li>
<li>Unordered List</li>
</ul>
<ol>
<li>Ordered List</li>
<li>Ordered List</li>
<li>Ordered List</li>
</ol>
</div>
</article>
<!-- VUE STUFF -->
<h2 class="heading-hr my-6 text-xl font-black">
<span class="flex-none">Some Vue Stuff</span>
</h2>
<ul class="mb-5 flex(& wrap) gap-5">
<li><btn variant="primary">Primary</btn></li>
<li><btn variant="secondary">Secondary</btn></li>
<li><btn>Default</btn>
</ul>
</section>
<section class="mx-auto px-4 max-w-prose">
<fieldset class="relative">
<label for="search" class="w-8 h-8 flex items-center justify-center absolute z-10 right-5 top-1/2 transform -translate-y-1/2"><i class="m-auto fa fa-fw fa-search" role="presentation" aria-label="Search"></i></label>
<input type="search" id="search" v-model="list_search" class="appearance-none py-3 px-6 w-full bg(white opacity-50) placeholder:(text-gray-400) block rounded-md motion-safe:(transition) focus:(bg-white outline-none shadow-xl ring(& white)) relative z-20 dark:(bg(gray-500 opacity-20) placeholder:(text-gray-500) focus:(bg-white text-gray-700))" placeholder="Search" />
</fieldset>
<section v-if="!list_results.length" class="py-8 text(gray-500 opacity-50) font-bold text-2xl uppercase tracking-tight text-center"><i class="fa fa-fw fa-ban opacity-50" role="presentation" aria-hidden="true"></i> No Results</section>
<ul class="mt-5 bg(white opacity-20) divide(y gray-500 opacity-20) rounded-lg shadow-lg dark:(bg-opacity-5)">
<li v-for="(item, index) in list_results" class="p-5 flex items-center gap-x-3 dark:()">
<i class="fa fa-fw fa-lg fa-check-circle opacity-30"></i>
<div>
{{ item.name }}
<ol v-if="item.tags" class="-mx-2 px-1 text-xs uppercase flex flex-wrap">
<li v-for="(tag, index) in item.tags" class="p-1"><b class="px-1 py-px inline-block rounded bg-gray-500 bg-opacity-25">{{ tag.name }}</b></li>
</ol>
</div>
</li>
</ul>
</section>
</main>
<footer class="mt-auto py-8 px-4 text(xs center gray-500) font-bold uppercase">
<div class="container mx-auto">
Made with
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" role="img" class="-mt-1 mx-1 fill-current text-red-400 inline-block">
<title>Love</title>
<polygon points="8 3 11 0 12 0 16 4 16 5 9 12 7 12 0 5 0 4 4 0 5 0" transform="translate(0,2)"></polygon>
</svg>
by <a href="https://craigerskine.com/" class="link mx-1">Craig Erskine</a>
</div>
</footer>
<div class="fixed top-2 right-2 z-50"><a @click.prevent="isDark = !isDark, dark(isDark)" href="#" :class="['w-7 h-7 bg(black opacity-10) text-gray-400 flex ring(1 current) rounded-full motion-safe:(transition) hover:(text-white)', isDark ? 'bg-opacity-50' : '']"><i class="m-auto fa fa-fw fa-moon"></i></a></div>
</div>
<div class="fixed inset-0 z-[-1] opacity-10 dark:(opacity-50)" aria-hidden="true"><div class="bg(grid fixed) absolute inset-0"></div></div>
<script src="https://cdn.jsdelivr.net/combine/npm/twind/twind.umd.js,npm/twind/observe/observe.umd.js,npm/twind/colors/colors.umd.js,npm/@twind/typography/typography.umd.js"></script>
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/@fortawesome/fontawesome-free/js/all.min.js"></script>
<script>
// data
var app = Vue.createApp({
data() {
return {
siteCompany: 'Company',
siteSlogan: 'Optional Slogan',
pageTitle: 'Some Title',
isDark: false,
nav: [
{ name: 'Nav Item 1', href: '#', },
{ name: 'Nav Item 2', href: '#', },
],
list_search: null,
list: [
{
name: 'Chicken colleries unexhaustibly',
tags: [{ name: 'Tag 1' }, { name: 'Tag 2' }, { name: 'Tag 3' },],
},
{
name: 'Chicken reliableness slippingly',
},
{
name: 'Monkey nyctalopy interacinous',
tags: [{ name: 'Tag 2' },],
},
{
name: 'Monkey surculose lovesickness',
},
{
name: 'Okapi nonfrosted manicure',
tags: [{ name: 'Tag 3' },],
},
],
}
},
methods: {
dark: function(mode){
if(mode){
document.documentElement.classList.toggle('dark');
} else {
document.documentElement.classList.remove('dark');
}
},
slug: function(text) {
return text
.toString()
.toLowerCase()
.trim()
.replace(/\.+/g, '-') // . to - (this can be removed - my specific usecase)
.normalize('NFKD') // unicode normalization
.replace(/[^\w\-]+/g, '') // remove other crazy chars
.replace(/\s+/g, '-') // space to -
.replace(/\-\-+/g, '-') // multiple - to single -
.replace(/^-+/, '') // remove - from start
.replace(/-+$/, '') // remove - from end
},
},
computed: {
list_results(){
if(this.list_search){
return this.list.filter((item)=>{
const tags = item.tags?.map(({
name
}) => name.toLowerCase()) ?? []
const arr = [item.name.toLowerCase(), ...tags]
return arr.some(e => e.includes(this.list_search))
})
} else {
return this.list;
}
}
},
mounted() {
document.title = this.pageTitle +' :: '+ this.siteCompany;
},
});
app.component('btn', {
template: `
<button :class="[
'px-2.5 py-1 inline-block rounded motion-safe:(transition)',
variant === 'primary' ? 'bg-pri-500 text-white hover:(bg-pri-700)' :
(variant === 'secondary') ? 'bg-sec-400 text-sec-900 hover:(bg-sec-600 text-white)' :
'text-gray-700 ring(1 inset current) hover:(opacity-60) dark:(text-gray-500)',
off ? 'bg-opacity-50 cursor-not-allowed' : ''
]" :disabled="off"><slot></slot></button>
`,
props: {
'variant': String,
'off': Boolean,
},
});
app.mount('.app');
twind.setup({
mode: 'silent',
darkMode: 'class',
theme: {
extend: {
colors: {
// brand colors
pri: twindColors.indigo,
sec: twindColors.yellow,
// extra colors
cyan: twindColors.cyan,
fuchsia: twindColors.fuchsia,
lime: twindColors.lime,
orange: twindColors.orange,
rose: twindColors.rose,
sky: twindColors.sky,
teal: twindColors.teal,
},
fontFamily: (theme) => ({
sans: 'Inter,'+ theme('fontFamily.sans'),
//mono: 'Inconsolata,'+ theme('fontFamily.mono'),
}),
},
},
plugins: {
...twindTypography(),
'scroll-smooth': { 'scroll-behavior': 'smooth' },
'ratio-16x9': { '@apply': 'pb-[56.25%] overflow-hidden relative' },
'ratio-4x3': { '@apply': 'pb-[75%] overflow-hidden relative' },
'bg-grid': { 'background-image': 'url("data:image/svg+xml,<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 4 4\' width=\'4\' height=\'4\'><rect x=\'0\' y=\'0\' width=\'2\' height=\'2\' fill=\'rgba(5,5,5,.33)\'></rect></svg>")', },
'link': { '@apply': 'text-pri-500 font-semibold motion-safe:(transition) hover:(text-pri-800 underline) dark:(text-pri-400 hover:(text-pri-100))' },
},
})
twind.tw(() => ({
'@global': {
'.prose': { '@apply': '!text-current dark:(text-current)' },
'.prose a': { '@apply': 'text-pri-500 font-semibold motion-safe:(transition) hover:(text-pri-800 underline) dark:(text-pri-400 hover:(text-pri-100))' },
'.prose h1,.prose h2,.prose h3': { '@apply': 'font-black' },
'.prose h4,.prose h5,.prose h6': { '@apply': 'font-bold' },
'.prose strong,.prose b': { '@apply': '!text-current dark:(text-current)' },
'.prose blockquote': { '@apply': 'border(l-4 gray-500 opacity-25) font-semibold italic' },
'.prose ul': { '@apply': 'ml-4 list-disc' },
'.prose ol': { '@apply': 'ml-4 list-decimal' },
'.prose ul > li,.prose ol > li': { 'padding-left': '.25rem !important' },
'.prose ul > li::marker,.prose ol > li::marker': { '@apply': 'text(gray-500 opacity-70))' },
'.prose hr': { '@apply': 'border(1 gray-500 opacity-25)' },
'.prose table': { '@apply': 'w-full' },
'.prose table th': { '@apply': 'text-left' },
'.prose code': { '@apply': 'font-bold' },
'.prose pre': { '@apply': 'bg-gray-800 text-gray-100 overflow-x-auto' },
//':root [v-cloak]': { '@apply': 'hidden', },
}
}))
twindObserve.observe(document.documentElement);
document.documentElement.removeAttribute('hidden');
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment