Skip to content

Instantly share code, notes, and snippets.

@craigerskine
Last active June 10, 2022 16:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save craigerskine/c974f33515c9553d82310001c0eb43c4 to your computer and use it in GitHub Desktop.
Save craigerskine/c974f33515c9553d82310001c0eb43c4 to your computer and use it in GitHub Desktop.
twind prose
<!doctype html>
<html lang="en" class="bg-gray-200 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:wght@100;400;600;700;900&display=swap" rel="stylesheet" />
</head>
<body class="text(gray-800)">
<div class="app min-h-screen flex flex-col divide(y gray-500 opacity-30)" v-cloak>
<header class="py-8 px-4 bg-pri-900 text-pri-400">
<div class="container mx-auto md:(flex items-center)">
<h1 class="py-2 text-3xl font-bold uppercase flex flex-wrap items-center justify-center md:(justify-start)">
<b class="mr-4 w-10 h-10 ring(1 current) text-pri-400 relative z-20 rounded-full"><b class="ring(1 pri-400) absolute inset-0 -z-10 rounded-full animate-ping"></b></b>
Title
<small class="ml-4 text-base opacity-50">Slogan</small>
</h1>
<nav class="py-5 md:ml-auto">
<ul class="flex flex-wrap items-center justify-center space-x-5 font-bold uppercase md:(justify-end)">
<li v-for="(item, index) in nav">
<a :href="item.href" class="group flex(& col) transition hover:(text-pri-100)">
<span>{{ item.name }}</span>
<b class="mt-1 w-full h-[2px] flex"><b class="mx-auto w-0 bg-current transition-all group-hover:(w-full)"></b></b>
</a>
</li>
</ul>
</nav>
</div>
<section class="mx-auto py-8 max-w-prose md:(w-3/4 max-w-none)">
<h2 class="first-line::(text-white font-black) font-bold leading-tight text-3xl md:(text-[calc(3vw+3vh+.5vmin)] tracking-tight)">Welcome to this amazing site! Let's do some fancy one-off font scaling <sup class="font-normal opacity-30">*</sup></h2>
<small class="pt-3 text-xs font-semibold block opacity-70 transform translate-y-8 md:(text-sm)">* Resize your viewport or rotate your device</small>
</section>
</header>
<main class="py-8">
<section class="mx-auto px-4 max-w-prose">
<article class="prose prose-pri pb-5">
<h3>Typography/Prose</h3>
<p>In a fantastical universe, centuries ago, Solveig was born into a family in a small fishing town in the cold North. Father fished, and Mother took care of the market stall to make their living.</p>
<ul>
<li v-for="i in [{title: 'Solveig', url: 'https://amzn.com/dp/B072ZCNFGP?tag=qrayg-20'},{title: 'Emerald Seas', url: 'https://amzn.com/dp/B07ZWBQK5L?tag=qrayg-20'},{title: 'Gods of Debauchery', url: 'https://amzn.com/dp/B096LSVYDL?tag=qrayg-20'}]"><a :href="i.url">{{ i.title }}</a></li>
</ul>
<p>Solveig never spent much time with the other children, instead preferring the company of the näcken boy who laughed and played violin while she danced by the river in the woods by her house, and of the strange old woman at the edge of town - unfailingly kind, always giving Solveig sweets, and rumoured to be a witch.</p>
<blockquote>
<p>Solveig's Father told her stories of the wider world, and all there was to see. He told her of the island in the East, at the edge of the world, where the Fountain of Youth runs...</p>
</blockquote>
<table class="striped">
<thead>
<tr>
<th v-for="i in 3">Header</th>
</tr>
</thead>
<tbody>
<tr v-for="i in 3">
<td v-for="i in 3">Cell</td>
</tr>
</tbody>
</table>
</article>
<article class="pb-16">
<div class="prose prose-pri pb-5">
<h3>Vue Component</h3>
</div>
<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>
</article>
</section>
<section class="mx-auto px-4 max-w-prose">
<div class="prose prose-pri pb-5">
<h3>Vue Live Filter <small class="ml-1 text-sm inline-block opacity-70">w/ nested array</small></h3>
</div>
<fieldset class="relative">
<label for="list-filter" class="w-8 h-8 flex items-center justify-center absolute z-10 right-5 top-1/2 opacity-50 transform -translate-y-1/2"><i class="m-auto fa fa-fw fa-filter" role="presentation" aria-label="Filter"></i></label>
<input type="search" id="list-filter" v-model="list_filter" class="appearance-none py-3 px-6 w-full bg(gray-500 opacity-10) block placeholder(gray-500 opacity-75) rounded-md transition focus:(bg-white outline-none shadow-xl ring(& white)) relative z-20" placeholder="Filter the following list..." />
</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="py-4">
<li v-for="(item, index) in list_results" class="py-2">
<a href="#" @click.prevent class="group p-4 flex items-center gap-4 rounded-lg transition hover:(bg(pri-600) text-white shadow-xl)">
<b class="w-12 h-12 bg(gray-500 opacity-30) text-gray-500 flex(& none) rounded-full transform scale-75 transition group-hover:(bg-pri-900 text-white scale-100 -rotate-[360deg])">
<i class="fa fa-check m-auto text-xl"></i>
</b>
<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 font-semibold bg(gray-500 opacity-25) inline-block rounded group-hover:(bg(pri-900 opacity-50))">{{ tag.name }}</b>
</li>
</ol>
</div>
</a>
</li>
</ul>
</section>
</main>
<footer class="mt-auto py-8 px-4 text(xs center) 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="mx-1 text-current transition">Craig Erskine</a>
</div>
</footer>
</div>
<div class="bg-gradient-to-b from-transparent via-transparent to-white fixed inset-0 z-[-1] opacity-10" 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 {
nav: [
{ name: 'Home', href: '#', },
{ name: 'Something', href: '#', },
{ name: 'Another', href: '#', },
],
list_filter: null,
list: [
{
name: 'When she was old enough, Solveig\'s Father taught her how to sail...',
tags: [
{ name: 'Tag 1' },
{ name: 'Tag 2' },
{ name: 'Tag 3' },
],
},
{
name: 'Not long before the strange old woman passed away, she blessed Solveig\'s boat - it would always take her to her heart\'s...',
},
{
name: 'Solveig comes of age, and decides to leave home, searching...',
tags: [
{ name: 'Tag 2' },
],
},
{
name: 'Passing years turn into a decade - collecting every clue...',
tags: [
{ name: 'Tag 1' },
],
},
{
name: 'At a dinner in a lively inn, she runs into her childhood friend...',
tags: [
{ name: 'Tag 3' },
],
},
],
}
},
methods: {
slug: function(text) {
const a = 'àáâäæãåāăąçćčđďèéêëēėęěğǵḧîïíīįìıİłḿñńǹňôöòóœøōõőṕŕřßśšşșťțûüùúūǘůűųẃẍÿýžźż·/_,:;'
const b = 'aaaaaaaaaacccddeeeeeeeegghiiiiiiiilmnnnnoooooooooprrsssssttuuuuuuuuuwxyyzzz------'
const p = new RegExp(a.split('').join('|'), 'g')
return text.toString().toLowerCase()
.replace(/\s+/g, '-') // Replace spaces with -
.replace(p, c => b.charAt(a.indexOf(c))) // Replace special characters
.replace(/&/g, '-and-') // Replace & with 'and'
.replace(/[^\w\-]+/g, '') // Remove all non-word characters
.replace(/\-\-+/g, '-') // Replace multiple - with single -
.replace(/^-+/, '') // Trim - from start of text
.replace(/-+$/, '') // Trim - from end of text
},
},
computed: {
// filter logic
list_results(){
if(this.list_filter){
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_filter))
})
} else {
return this.list;
}
}
},
mounted() {
// modify the page title
document.title = 'Some Title';
},
});
// component example
app.component('btn', {
template: `
<button :class="[
'px-2.5 py-1 inline-block rounded transition',
variant === 'primary' ? 'bg-pri-500 text-white hover:(bg-pri-700)' :
(variant === 'secondary') ? 'bg-gray-400 text-gray-900 hover:(bg-gray-600 text-white)' :
'ring(1 inset gray-500 opacity-50) hover:(opacity-60)',
off ? 'bg-opacity-50 cursor-not-allowed' : ''
]" :disabled="off"><slot></slot></button>
`,
props: {
'variant': String,
'off': Boolean,
},
});
app.mount('.app');
twind.setup({
mode: "silent",
theme: {
extend: {
colors: {
pri: twindColors.blue,
gray: twindColors.gray,
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: {
// typography
...twindTypography(),
// my custom stuff
'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>")', },
},
})
twind.tw(() => ({
'@global': {
// some prose helpers
'.prose h1,.prose h2,.prose h3': { '@apply': 'font-black' },
'.prose h4,.prose h5,.prose h6': { '@apply': 'font-bold' },
'.prose hr': { '@apply': 'border(pri-500 opacity-30)' },
'.prose blockquote': { '@apply': 'border(l-4 pri-900 opacity-10) 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-pri-900 text-opacity-50' },
'.prose table': { '@apply': 'w-full' },
'.prose table thead tr': { '@apply': 'border(b-2 gray-500 opacity-20)' },
'.prose table thead th': { '@apply': 'text-left uppercase' },
'.prose table tbody tr': { '@apply': 'border(gray-500 opacity-20)' },
'.prose table.striped tbody tr:nth-child(even)': { '@apply': 'bg(gray-500 opacity-10)' },
'.prose table.striped tbody td:first-child': { '@apply': 'pl-2' },
'.prose code': { '@apply': 'font-bold' },
'.prose pre': { '@apply': 'bg-gray-800 text-gray-100 overflow-x-auto' },
// prevent fouc for vue
':root [v-cloak]': { '@apply': 'hidden', },
}
}))
twindObserve.observe(document.documentElement);
document.documentElement.removeAttribute('hidden');
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en" class="bg-gray-100" hidden>
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Local Prototype - NO BUILD</title>
<link href="https://fonts.gstatic.com" rel="preconnect" />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100;400;700;900&display=swap" rel="stylesheet" />
</head>
<body class="text-gray-700">
<div id="app" class="min-h-screen flex(& col) divide(y gray-500 opacity-10)" v-cloak>
<header class="p-4 bg(grid center) flex-none">
<nav class="container mx-auto">
<ul class="flex items-center">
<li class="flex-none">
<a href="#" class="w-10 h-10 bg(gray-500 opacity-50) flex rounded-full transition hover:(bg-transparent ring(4 primary-500 opacity-75)) focus:(bg-transparent ring(4 primary-500 opacity-75))"></a>
</li>
<li class="flex flex-1">
<ul class="ml-auto flex items-center space-x-5">
<li v-for="i in 3"><a href="#" class="w-10 h-5 block bg(gray-500 opacity-50) rounded-full transition hover:(bg-transparent ring(4 primary-500 opacity-75)) focus:(bg-transparent ring(4 primary-500 opacity-75)) md:(w-16)"></a></li>
</ul>
</li>
</ul>
</nav>
</header>
<main class="p-4 bg-white flex-1">
<section class="prose">
<h1>Heading 1</h1>
<h2>Heading 2</h2>
<h3>Heading 3</h3>
<h4>Heading 4</h4>
<h5>Heading 5</h5>
<h6>Heading 6</h6>
<p>Paragraph... lorem ipsum dolor sit amet <a href="#">consectetur</a> adipisicing elit. Eaque maxime minus, molestiae doloribus possimus tempora similique velit eveniet id, repellendus reiciendis. Repellendus eius fugiat aperiam, aliquid quo dicta molestias soluta.</p>
<blockquote>
<p>Blockquote Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quidem perspiciatis incidunt expedita repellendus nobis! Commodi, accusamus autem! Aut earum, corrupti porro nam distinctio itaque saepe!</p>
<p>Last element removes bottom spacing for consistent flow...</p>
</blockquote>
<figure>
<img src="https://source.unsplash.com/random/800x400?chicken" alt="image" />
<figcaption>Responsive image w/ caption</figcaption>
</figure>
<figure class="block-full">
<img src="https://source.unsplash.com/random/1200x900?sunset" alt="Some Image" class="w-full h-full object(cover center) absolute inset-0" />
<figcaption class="p-3 max-w-full bg(gray-900 opacity-80) block text-white absolute bottom-0 left-0"><i class="text-sm min-w-0 block truncate">Full width w/ caption</i></figcaption>
</figure>
<div class="md:(grid(& flow-row cols-2) gap-x-5)">
<ul>
<li v-for="i in 3">Unordered list
<ol v-if="i === 2">
<li>Nested</li>
</ol>
</li>
</ul>
<ol>
<li v-for="i in 3">Ordered list
<ul v-if="i === 2">
<li>Nested</li>
</ul>
</li>
</ol>
</div>
<hr />
<table class="table">
<thead>
<tr>
<th v-for="i in 3">Table header</th>
</tr>
</thead>
<tbody>
<tr v-for="i in 3">
<td v-for="i in 3">Table cell</td>
</tr>
</tbody>
</table>
</section>
<section class="-mb-4 -mx-4 py-16 bg-gray-700">
<ul class="container mx-auto flex(& col) items-center justify-center space-y-10 md:(flex-row space-y-0 space-x-10)">
<li v-for="i in 3" class="p-4 w-full max-w-sm text-gray-500 flex-1 ring(2 current) rounded-md animate-pulse">
<div class="h-full flex items-center justify-center space-x-5">
<div class="w-12 h-12 bg-current flex-none rounded-full"></div>
<div class="flex-1 flex flex-col space-y-3">
<div class="w-full h-6 bg-current rounded-md"></div>
<div class="w-2/3 h-6 bg-current rounded-md"></div>
</div>
</div>
</li>
</ul>
</section>
</main>
<footer class="mt-auto py-8 px-5 bg(grid center) text(xs center) font-bold uppercase flex-none">
<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="mx-1 text-current transition">Craig Erskine</a>
</div>
</footer>
</div>
<script type="importmap">
{
"imports": {
"vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.prod.js",
"headlessui": "https://unpkg.com/@headlessui/vue@1/dist/headlessui.esm.js"
}
}
</script>
<script type="module">
// vue
import { createApp } from 'vue'
createApp({
data() {
return {
}
}
}).mount('#app')
// headlessui
import { Menu, MenuButton, MenuItems, MenuItem } from 'headlessui';
export default {
components: {
Menu,
MenuButton,
MenuItems,
MenuItem,
},
};
// twind
import { tw, setup, silent } from 'https://cdn.skypack.dev/twind';
import { css, theme, apply } from 'https://cdn.skypack.dev/twind/css';
import * as colors from 'https://cdn.skypack.dev/twind/colors';
import { observe } from 'https://cdn.skypack.dev/twind/observe';
setup({
mode: silent,
theme: {
extend: {
colors: {
gray: colors.gray,
primary: colors.blue,
},
fontFamily: (theme) => ({
sans: 'Inter,'+ theme('fontFamily.sans'),
serif: 'PT Serif,'+ theme('fontFamily.serif'),
mono: 'Inconsolata,'+ theme('fontFamily.mono'),
}),
},
},
plugins: {
'block-full': { '@apply': 'mb-5 -ml-[50vw] -mr-[50vw] w-screen min-h-[400px] relative left-1/2 right-1/2' },
'scroll-smooth': { 'scroll-behavior': 'smooth' },
'ratio-16x9': { '@apply': 'pb-[56.25%] overflow-hidden relative' },
'ratio-4x3': { '@apply': 'pb-[75%] overflow-hidden relative' },
'bg-dot': { '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(128,128,128,.1)\'></rect></svg>")', },
'bg-grid': { 'background-image': 'url("data:image/svg+xml,<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 32 32\' width=\'32\' height=\'32\' fill=\'none\' stroke=\'rgba(128,128,128,.1)\'><path d=\'M0 .5H31.5V32\'/></svg>")', },
},
})
tw(() => ({
'@global': {
'.prose': { '@apply': 'mx-auto max-w-prose' },
'.prose a:not([class])': { '@apply': 'text-indigo-500 hover:(text-primary-400 underline) focus:(text-primary-400 underline) font-bold transition', },
'.prose p:not([class]),.prose figure:not([class]),.prose ul:not([class]),.prose ol:not([class]),.prose .table': { '@apply': 'mb-8 border-current', },
'.prose blockquote:not([class])': { '@apply': 'mb-8 p-5 border(l-4 gray-500 opacity-50) bg(gray-500 opacity-10) italic', },
'.prose blockquote:not([class]) > p:last-child': { '@apply': 'mb-0' },
'.prose figure:not([class]) > figcaption:not([class])': { '@apply': 'pt-1 text-sm italic block' },
'.prose ul:not([class])': { '@apply': 'ml-5 list(disc)', },
'.prose ol:not([class])': { '@apply': 'ml-5 list(decimal)', },
'.prose ul:not([class]) ul:not([class]),.prose ul:not([class]) ol:not([class]),.prose ol:not([class]) ol:not([class]),.prose ol:not([class]) ul:not([class])': { '@apply': 'mb-0', },
'.prose hr:not([class])': { '@apply': 'mb-8 border-current opacity-25', },
'.prose h1:not([class]),.prose h2:not([class]),.prose h3:not([class]),.prose h4:not([class]),.prose h5:not([class]),.prose h6:not([class])': { '@apply': 'mb-4 text-4xl text-gray-900 font-black', },
'.prose h2:not([class])': { '@apply': 'text-3xl', },
'.prose h3:not([class])': { '@apply': 'text-2xl', },
'.prose h4:not([class])': { '@apply': 'text-xl font-bold', },
'.prose h5:not([class])': { '@apply': 'text-lg font-bold', },
'.prose h6:not([class])': { '@apply': 'text-base font-bold', },
'.prose .table': { '@apply': 'w-full divide(y gray-500 opacity-20)', },
'.prose .table thead tr': { '@apply': 'text-sm uppercase opacity-50', },
'.prose .table th,.prose .table td': { '@apply': 'py-1 px-5 text-left', },
'.prose .table tbody': { '@apply': 'divide(y gray-500 opacity-20)', },
'.prose .table tbody tr:nth-child(even)': { '@apply': 'bg-gray-500 bg-opacity-5', },
//':root [v-cloak]': { '@apply': 'hidden', },
}
}))
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