├── public ├── favicon.ico ├── hello-friend-ng.png ├── cover.svg ├── favicon.svg └── terminal-preview.svg ├── .vscode ├── extensions.json └── launch.json ├── tsconfig.json ├── src ├── components │ ├── FormattedDate.astro │ └── PostCard.astro ├── content.config.ts ├── pages │ ├── rss.xml.js │ ├── 404.astro │ ├── posts │ │ ├── index.astro │ │ └── [...slug].astro │ ├── index.astro │ ├── about.astro │ └── tags │ │ ├── [tag].astro │ │ └── index.astro ├── content │ └── posts │ │ ├── hello-friend.md │ │ ├── rich-content.md │ │ ├── showcase.md │ │ ├── code-blocks-examples.md │ │ └── lorem-ipsum.md ├── styles │ ├── fonts.css │ ├── header.css │ ├── footer.css │ ├── buttons.css │ ├── pagination.css │ ├── gist.css │ ├── code.css │ ├── menu.css │ ├── terms.css │ ├── main.css │ ├── syntax.css │ ├── post.css │ └── terminal.css └── layouts │ ├── PostLayout.astro │ └── BaseLayout.astro ├── .gitignore ├── astro.config.mjs ├── package.json ├── LICENSE ├── .github └── workflows │ └── deploy.yml └── README.md /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/hello-friend-ng.png: -------------------------------------------------------------------------------- 1 | placeholder -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict", 3 | "include": [".astro/types.d.ts", "**/*"], 4 | "exclude": ["dist"] 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /src/components/FormattedDate.astro: -------------------------------------------------------------------------------- 1 | --- 2 | export interface Props { 3 | date: Date; 4 | } 5 | 6 | const { date } = Astro.props; 7 | const formattedDate = date.toISOString().split('T')[0]; 8 | --- 9 | 10 | -------------------------------------------------------------------------------- /public/cover.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | Hello Friend 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | >_ 5 | 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | # generated types 4 | .astro/ 5 | 6 | # dependencies 7 | node_modules/ 8 | 9 | # logs 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | 23 | # jetbrains setting folder 24 | .idea/ 25 | -------------------------------------------------------------------------------- /astro.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { defineConfig } from 'astro/config'; 3 | import sitemap from '@astrojs/sitemap'; 4 | 5 | // https://astro.build/config 6 | export default defineConfig({ 7 | site: 'https://dennisklappe.github.io', 8 | // Only use base path in production (GitHub Pages) 9 | base: process.env.NODE_ENV === 'production' ? '/astro-theme-terminal' : '/', 10 | integrations: [sitemap()], 11 | markdown: { 12 | shikiConfig: { 13 | theme: 'css-variables', 14 | langs: [], 15 | wrap: true, 16 | }, 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /src/content.config.ts: -------------------------------------------------------------------------------- 1 | import { defineCollection, z } from 'astro:content'; 2 | 3 | const posts = defineCollection({ 4 | type: 'content', 5 | schema: z.object({ 6 | title: z.string(), 7 | description: z.string().optional(), 8 | pubDate: z.coerce.date(), 9 | updatedDate: z.coerce.date().optional(), 10 | author: z.string().optional(), 11 | image: z.string().optional(), 12 | externalLink: z.string().optional(), 13 | tags: z.array(z.string()).default([]), 14 | draft: z.boolean().default(false), 15 | }), 16 | }); 17 | 18 | export const collections = { posts }; -------------------------------------------------------------------------------- /src/pages/rss.xml.js: -------------------------------------------------------------------------------- 1 | import rss from '@astrojs/rss'; 2 | import { getCollection } from 'astro:content'; 3 | 4 | export async function GET(context) { 5 | const posts = await getCollection('posts'); 6 | return rss({ 7 | title: 'Astro Terminal Theme', 8 | description: 'A terminal-inspired theme for Astro', 9 | site: context.site, 10 | items: posts.map((post) => ({ 11 | title: post.data.title, 12 | pubDate: post.data.pubDate, 13 | description: post.data.description, 14 | link: `/posts/${post.slug}/`, 15 | })), 16 | customData: `en-us`, 17 | }); 18 | } -------------------------------------------------------------------------------- /src/pages/404.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import BaseLayout from '../layouts/BaseLayout.astro'; 3 | 4 | const missingBackButtonLabel = "Back to home page"; 5 | const base = import.meta.env.BASE_URL.endsWith('/') ? import.meta.env.BASE_URL : import.meta.env.BASE_URL + '/'; 6 | --- 7 | 8 | 9 |
10 |

404 — Page not found...

11 |

The page you're looking for doesn't exist. It might have been moved, deleted, or you may have entered the wrong URL.

12 |
13 | 14 | {missingBackButtonLabel} → 15 | 16 |
17 |
18 |
19 | 20 | -------------------------------------------------------------------------------- /src/content/posts/hello-friend.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Hello Friend' 3 | description: 'Welcome in demo of the Terminal theme. Please, look around and check whether this is something for you. And if you like it, give it a chance. Anddddd just one more small thing. You can also set **coverImage** for your posts...' 4 | pubDate: 2022-01-25 5 | author: 'Radek' 6 | image: 'https://panr.github.io/hugo-theme-terminal-demo/posts/hello/cover.jpg' 7 | tags: [] 8 | --- 9 | 10 | Welcome in demo of the `Terminal` theme. Please, look around and check whether this is something for you. And if you like it, give it a chance. 11 | 12 | Also, if you start a blog, remember about this quote: 13 | 14 | > "Don't worry about people stealing your ideas. If your ideas are any good, you'll have to ram them down people's throats." 15 | 16 | And one more small thing. You can also set **coverImage** for your posts... -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "astro-theme-terminal", 3 | "type": "module", 4 | "version": "1.0.0", 5 | "description": "A terminal-inspired theme for Astro, ported from Hugo Terminal Theme by panr", 6 | "license": "MIT", 7 | "author": "Dennis Klappe", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/dennisklappe/astro-theme-terminal" 11 | }, 12 | "keywords": [ 13 | "astro", 14 | "theme", 15 | "terminal", 16 | "blog", 17 | "monospace", 18 | "retro" 19 | ], 20 | "scripts": { 21 | "dev": "astro dev", 22 | "check": "astro check", 23 | "build": "astro check && astro build", 24 | "preview": "astro preview", 25 | "astro": "astro" 26 | }, 27 | "dependencies": { 28 | "astro": "^5.15.7" 29 | }, 30 | "devDependencies": { 31 | "@astrojs/check": "^0.9.5", 32 | "@astrojs/rss": "^4.0.13", 33 | "@astrojs/sitemap": "^3.6.0", 34 | "typescript": "^5.9.3" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/pages/posts/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import BaseLayout from '../../layouts/BaseLayout.astro'; 3 | import PostCard from '../../components/PostCard.astro'; 4 | import { getCollection } from 'astro:content'; 5 | 6 | const posts = (await getCollection('posts')) 7 | .filter(post => !post.data.draft) 8 | .sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf()); 9 | --- 10 | 11 | 12 |
13 |

Posts

14 | {posts.length === 0 ? ( 15 |

No posts yet. Check back soon!

16 | ) : ( 17 | posts.map((post) => ) 18 | )} 19 |
20 |
21 | 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Astro Theme Terminal Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/styles/fonts.css: -------------------------------------------------------------------------------- 1 | /* Font loading is handled in terminal.css */ 2 | /* This file is kept for compatibility with Hugo theme structure */ 3 | 4 | /* Additional font-related styles */ 5 | .font-light { 6 | font-weight: 300; 7 | } 8 | 9 | .font-regular { 10 | font-weight: 400; 11 | } 12 | 13 | .font-medium { 14 | font-weight: 500; 15 | } 16 | 17 | .font-semibold { 18 | font-weight: 600; 19 | } 20 | 21 | .font-bold { 22 | font-weight: 700; 23 | } 24 | 25 | /* Text transforms */ 26 | .uppercase { 27 | text-transform: uppercase; 28 | letter-spacing: 0.04em; 29 | } 30 | 31 | .lowercase { 32 | text-transform: lowercase; 33 | } 34 | 35 | .capitalize { 36 | text-transform: capitalize; 37 | } 38 | 39 | /* Font size utilities */ 40 | .text-xs { 41 | font-size: calc(var(--font-size) * 0.75); 42 | } 43 | 44 | .text-sm { 45 | font-size: calc(var(--font-size) * 0.875); 46 | } 47 | 48 | .text-base { 49 | font-size: var(--font-size); 50 | } 51 | 52 | .text-lg { 53 | font-size: calc(var(--font-size) * 1.125); 54 | } 55 | 56 | .text-xl { 57 | font-size: calc(var(--font-size) * 1.25); 58 | } 59 | 60 | .text-2xl { 61 | font-size: calc(var(--font-size) * 1.5); 62 | } -------------------------------------------------------------------------------- /src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import BaseLayout from '../layouts/BaseLayout.astro'; 3 | import PostCard from '../components/PostCard.astro'; 4 | import { getCollection } from 'astro:content'; 5 | 6 | const posts = (await getCollection('posts')) 7 | .filter(post => !post.data.draft) 8 | .sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf()); 9 | --- 10 | 11 | 12 |
13 |

Hello there!

14 |

Welcome to the Astro Terminal theme! This is a port of the popular Hugo Terminal theme by panr, bringing the same retro terminal aesthetic to Astro.

15 |

16 | You can customise the colour scheme using panr's Terminal.css 17 | generator - all generated schemes are fully compatible with this theme. 18 |

19 |

20 | 21 | Terminal.css Preview 22 | 23 |

24 |
25 | 26 |
27 | {posts.map((post) => )} 28 |
29 |
-------------------------------------------------------------------------------- /src/pages/about.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import BaseLayout from '../layouts/BaseLayout.astro'; 3 | --- 4 | 5 | 6 |
7 |

About

8 |

9 | This is the Astro Terminal theme, a port of the fantastic 10 | Hugo Terminal theme 11 | created by panr. 12 |

13 | 14 |

15 | The Astro version maintains all the great features of the original theme whilst using Astro's modern 16 | static site generation capabilities. 17 |

18 | 19 |

20 | The theme features syntax highlighting with Shiki, customisable colour schemes via 21 | Terminal.css, 22 | Fira Code monospace font, and a beautiful retro terminal aesthetic. 23 |

24 | 25 |

26 | Ported to Astro by Dennis Klappe. 27 |

28 |
29 |
30 | 31 | -------------------------------------------------------------------------------- /src/styles/header.css: -------------------------------------------------------------------------------- 1 | /* Header */ 2 | .header { 3 | display: flex; 4 | flex-direction: column; 5 | position: relative; 6 | } 7 | 8 | .header__inner { 9 | display: flex; 10 | align-items: center; 11 | justify-content: space-between; 12 | } 13 | 14 | .header__logo { 15 | display: flex; 16 | flex: 1; 17 | align-items: center; 18 | } 19 | 20 | .header__logo::after { 21 | content: ""; 22 | background: repeating-linear-gradient(90deg, var(--accent), var(--accent) 2px, transparent 0, transparent 10px); 23 | display: block; 24 | width: 100%; 25 | height: 35px; 26 | margin-left: 10px; 27 | } 28 | 29 | .header__logo a { 30 | flex: none; 31 | max-width: 100%; 32 | text-decoration: none; 33 | } 34 | 35 | .logo { 36 | display: flex; 37 | align-items: center; 38 | text-decoration: none; 39 | background: var(--accent); 40 | color: var(--background); 41 | font-weight: 700; 42 | padding: 5px 10px; 43 | } 44 | 45 | /* Responsive */ 46 | @media (max-width: 684px) { 47 | .header__inner { 48 | flex-direction: row; 49 | align-items: center; 50 | justify-content: space-between; 51 | width: 100%; 52 | } 53 | 54 | .header__logo { 55 | margin-bottom: 0; 56 | flex: 1; 57 | } 58 | 59 | .header__logo::after { 60 | display: block; 61 | width: auto; 62 | flex: 1; 63 | margin-left: 10px; 64 | margin-right: 10px; 65 | } 66 | } -------------------------------------------------------------------------------- /src/pages/tags/[tag].astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { getCollection } from 'astro:content'; 3 | import BaseLayout from '../../layouts/BaseLayout.astro'; 4 | import PostCard from '../../components/PostCard.astro'; 5 | 6 | export async function getStaticPaths() { 7 | const allPosts = await getCollection('posts'); 8 | const uniqueTags = [...new Set(allPosts.map(post => post.data.tags).flat())]; 9 | 10 | return uniqueTags.map(tag => { 11 | const filteredPosts = allPosts 12 | .filter(post => post.data.tags?.includes(tag) && !post.data.draft) 13 | .sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf()); 14 | return { 15 | params: { tag }, 16 | props: { posts: filteredPosts } 17 | }; 18 | }); 19 | } 20 | 21 | const { tag } = Astro.params; 22 | const { posts } = Astro.props; 23 | --- 24 | 25 | 26 |
27 |

28 | Posts for: #{tag} 29 |

30 | {posts.length === 0 ? ( 31 |

No posts found with this tag.

32 | ) : ( 33 | posts.map((post) => ) 34 | )} 35 |
36 |
37 | 38 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to GitHub Pages 2 | 3 | on: 4 | # Trigger the workflow every time you push to the `main` branch 5 | # Using a different branch name? Replace `main` with your branch's name 6 | push: 7 | branches: [ main ] 8 | # Allows you to run this workflow manually from the Actions tab on GitHub. 9 | workflow_dispatch: 10 | 11 | # Allow this job to clone the repo and create a page deployment 12 | permissions: 13 | contents: read 14 | pages: write 15 | id-token: write 16 | 17 | jobs: 18 | build: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Checkout your repository using git 22 | uses: actions/checkout@v5 23 | - name: Install, build, and upload your site 24 | uses: withastro/action@v5 25 | # with: 26 | # path: . # The root location of your Astro project inside the repository. (optional) 27 | # node-version: 24 # The specific version of Node that should be used to build your site. Defaults to 22. (optional) 28 | # package-manager: pnpm@latest # The Node package manager that should be used to install dependencies and build your site. Automatically detected based on your lockfile. (optional) 29 | # build-cmd: pnpm run build # The command to run to build your site. Runs the package build script/task by default. (optional) 30 | deploy: 31 | needs: build 32 | runs-on: ubuntu-latest 33 | environment: 34 | name: github-pages 35 | url: ${{ steps.deployment.outputs.page_url }} 36 | steps: 37 | - name: Deploy to GitHub Pages 38 | id: deployment 39 | uses: actions/deploy-pages@v4 -------------------------------------------------------------------------------- /src/pages/posts/[...slug].astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { getCollection } from 'astro:content'; 3 | import PostLayout from '../../layouts/PostLayout.astro'; 4 | 5 | export async function getStaticPaths() { 6 | const posts = (await getCollection('posts')) 7 | .filter(post => !post.data.draft) 8 | .sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf()); 9 | 10 | return posts.map((post, index) => ({ 11 | params: { slug: post.slug }, 12 | props: { 13 | post, 14 | prevPost: index < posts.length - 1 ? posts[index + 1] : null, 15 | nextPost: index > 0 ? posts[index - 1] : null, 16 | }, 17 | })); 18 | } 19 | 20 | const { post, prevPost, nextPost } = Astro.props; 21 | const { Content } = await post.render(); 22 | const base = import.meta.env.BASE_URL.endsWith('/') ? import.meta.env.BASE_URL : import.meta.env.BASE_URL + '/'; 23 | --- 24 | 25 | 26 | 27 | 28 | 49 | -------------------------------------------------------------------------------- /public/terminal-preview.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Terminal Theme 11 | 12 | 13 | $ theme --preview 14 | Loading Terminal theme... 15 | [████████████████████] 100% 16 | Features: 17 | • Retro terminal aesthetic 18 | • Syntax highlighting 19 | • Fully responsive 20 | • Dark mode ready 21 | $ _ 22 | -------------------------------------------------------------------------------- /src/styles/footer.css: -------------------------------------------------------------------------------- 1 | /* Footer */ 2 | .footer { 3 | padding: 40px 0; 4 | flex-grow: 0; 5 | opacity: .65; 6 | } 7 | 8 | .footer__inner { 9 | display: flex; 10 | align-items: center; 11 | justify-content: space-between; 12 | margin: 0; 13 | max-width: 100%; 14 | } 15 | 16 | .footer a { 17 | color: inherit; 18 | } 19 | 20 | .footer .copyright { 21 | display: flex; 22 | flex-flow: row wrap; 23 | flex: 1; 24 | align-items: center; 25 | justify-content: center; 26 | } 27 | 28 | .footer .copyright--user { 29 | margin: auto; 30 | text-align: center; 31 | } 32 | 33 | .footer .copyright > *:first-child:not(:only-child) { 34 | margin-right: 10px; 35 | } 36 | 37 | .footer .copyright span { 38 | white-space: nowrap; 39 | } 40 | 41 | .footer .copyright span:not(:last-child) { 42 | margin-right: 5px; 43 | } 44 | 45 | /* Responsive */ 46 | @media (max-width: 900px) { 47 | .footer__inner { 48 | flex-direction: column; 49 | } 50 | } 51 | 52 | @media (max-width: 684px) { 53 | .footer { 54 | padding: 20px 0; 55 | } 56 | 57 | .footer__inner { 58 | padding: 0 10px; 59 | } 60 | 61 | .copyright { 62 | flex-wrap: wrap; 63 | text-align: center; 64 | gap: 5px; 65 | } 66 | 67 | .copyright span { 68 | font-size: 14px; 69 | line-height: 1.6; 70 | white-space: normal; 71 | } 72 | 73 | /* Hide separators on mobile for cleaner layout */ 74 | .copyright .separator { 75 | display: none; 76 | } 77 | 78 | /* Each text span on its own line on mobile */ 79 | .copyright span:not(.separator) { 80 | display: block; 81 | width: 100%; 82 | } 83 | 84 | /* Make links more touch-friendly on mobile */ 85 | .footer a { 86 | padding: 2px 4px; 87 | display: inline-block; 88 | } 89 | } -------------------------------------------------------------------------------- /src/components/PostCard.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import FormattedDate from './FormattedDate.astro'; 3 | 4 | export interface Props { 5 | post: { 6 | slug: string; 7 | data: { 8 | title: string; 9 | description?: string; 10 | pubDate: Date; 11 | author?: string; 12 | tags?: string[]; 13 | image?: string; 14 | externalLink?: string; 15 | }; 16 | }; 17 | } 18 | 19 | const { post } = Astro.props; 20 | const base = import.meta.env.BASE_URL.endsWith('/') ? import.meta.env.BASE_URL : import.meta.env.BASE_URL + '/'; 21 | const postUrl = post.data.externalLink || `${base}posts/${post.slug}/`; 22 | const isExternal = !!post.data.externalLink; 23 | --- 24 | 25 |
26 |

27 | 28 | {post.data.title} 29 | 30 |

31 |
32 | 35 | {post.data.author && ( 36 | 37 | )} 38 |
39 | {post.data.tags && post.data.tags.length > 0 && ( 40 | 41 | {post.data.tags.map((tag) => ( 42 | <> 43 | {tag} 44 | 45 | ))} 46 | 47 | )} 48 | {post.data.image && ( 49 |
50 | {post.data.title} 51 |
52 | )} 53 | {post.data.description && ( 54 |
55 |

{post.data.description}

56 |
57 | )} 58 |
59 | 60 | [Read more] 61 | 62 |
63 |
-------------------------------------------------------------------------------- /src/pages/tags/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { getCollection } from 'astro:content'; 3 | import BaseLayout from '../../layouts/BaseLayout.astro'; 4 | 5 | const allPosts = await getCollection('posts'); 6 | const tags = [...new Set(allPosts.map(post => post.data.tags).flat())]; 7 | const tagCounts: Record = {}; 8 | 9 | // Count posts per tag 10 | allPosts.forEach(post => { 11 | post.data.tags?.forEach(tag => { 12 | tagCounts[tag] = (tagCounts[tag] || 0) + 1; 13 | }); 14 | }); 15 | 16 | // Sort tags alphabetically 17 | const sortedTags = tags.sort((a, b) => a.localeCompare(b)); 18 | const base = import.meta.env.BASE_URL.endsWith('/') ? import.meta.env.BASE_URL : import.meta.env.BASE_URL + '/'; 19 | --- 20 | 21 | 22 |
23 |

Tags

24 | 25 |
26 | {sortedTags.map(tag => ( 27 | 28 | #{tag} 29 | ({tagCounts[tag]}) 30 | 31 | ))} 32 |
33 |
34 |
35 | 36 | -------------------------------------------------------------------------------- /src/styles/buttons.css: -------------------------------------------------------------------------------- 1 | /* Buttons container */ 2 | .buttons { 3 | display: flex; 4 | flex-wrap: wrap; 5 | justify-content: center; 6 | gap: 10px; 7 | margin: 40px 0; 8 | } 9 | 10 | /* Button variations */ 11 | .button { 12 | position: relative; 13 | display: inline-flex; 14 | align-items: center; 15 | justify-content: center; 16 | font: inherit; 17 | font-weight: 700; 18 | text-decoration: none; 19 | text-align: center; 20 | background: transparent; 21 | color: var(--accent); 22 | padding: 5px 18px; 23 | border: 4px solid var(--accent); 24 | border-radius: var(--radius); 25 | transition: background .15s linear; 26 | appearance: none; 27 | cursor: pointer; 28 | outline: none; 29 | } 30 | 31 | .button:hover { 32 | background: color-mix(in srgb, var(--accent) 15%, transparent); 33 | } 34 | 35 | .button:focus-visible { 36 | outline: 1px solid var(--accent); 37 | outline-offset: 2px; 38 | } 39 | 40 | /* Button styles */ 41 | .button.outline { 42 | background: transparent; 43 | box-shadow: none; 44 | padding: 8px 18px; 45 | } 46 | 47 | .button.link { 48 | background: none; 49 | font-size: 1rem; 50 | border: none; 51 | text-decoration: underline; 52 | text-underline-offset: 3px; 53 | padding: 0; 54 | } 55 | 56 | .button.link:hover { 57 | background: none; 58 | text-decoration: none; 59 | } 60 | 61 | .button.small { 62 | font-size: calc(var(--font-size) * .8); 63 | } 64 | 65 | .button.wide { 66 | min-width: 200px; 67 | padding: 14px 24px; 68 | } 69 | 70 | /* Inline button (read more links) */ 71 | .button.inline { 72 | background: transparent; 73 | border: none; 74 | padding: 0; 75 | text-decoration: underline; 76 | text-underline-offset: 3px; 77 | font-weight: 400; 78 | } 79 | 80 | .button.inline:hover { 81 | text-decoration: none; 82 | background: transparent; 83 | } 84 | 85 | /* Read more link */ 86 | .read-more { 87 | display: inline-block; 88 | text-decoration: none; 89 | font-weight: 700; 90 | color: var(--accent); 91 | margin-top: 10px; 92 | } -------------------------------------------------------------------------------- /src/styles/pagination.css: -------------------------------------------------------------------------------- 1 | /* Pagination */ 2 | .pagination { 3 | margin-top: 50px; 4 | } 5 | 6 | .pagination__title { 7 | display: flex; 8 | text-align: center; 9 | position: relative; 10 | margin: 20px 0; 11 | } 12 | 13 | .pagination__title-h { 14 | text-align: center; 15 | margin: 0 auto; 16 | padding: 5px 10px; 17 | background: var(--background); 18 | color: color-mix(in srgb, var(--foreground) 65%, transparent); 19 | font-size: calc(var(--font-size) * .8); 20 | text-transform: uppercase; 21 | text-decoration: none; 22 | letter-spacing: .1em; 23 | z-index: 1; 24 | } 25 | 26 | .pagination__title hr { 27 | position: absolute; 28 | left: 0; 29 | right: 0; 30 | width: 100%; 31 | margin-top: 15px; 32 | z-index: 0; 33 | border: none; 34 | border-top: 1px solid color-mix(in srgb, var(--foreground) 10%, transparent); 35 | } 36 | 37 | .pagination__buttons { 38 | display: flex; 39 | align-items: center; 40 | justify-content: center; 41 | flex-wrap: wrap; 42 | gap: 10px; 43 | text-align: center; 44 | } 45 | 46 | .button.previous, 47 | .button.next { 48 | position: relative; 49 | } 50 | 51 | .button.previous { 52 | margin-right: auto; 53 | } 54 | 55 | .button.next { 56 | margin-left: auto; 57 | } 58 | 59 | /* Post navigation - override for centered layout */ 60 | .pagination__buttons .button.inline.prev, 61 | .pagination__buttons .button.inline.next { 62 | margin: 0; 63 | } 64 | 65 | /* Page numbers */ 66 | .button.number { 67 | font-weight: 700; 68 | min-width: 40px; 69 | padding: 5px 10px; 70 | } 71 | 72 | .button.number.active { 73 | background: var(--accent); 74 | color: var(--background); 75 | } 76 | 77 | /* Pagination info */ 78 | .pagination-info { 79 | text-align: center; 80 | margin: 20px 0; 81 | color: color-mix(in srgb, var(--foreground) 65%, transparent); 82 | font-size: calc(var(--font-size) * .9); 83 | } 84 | 85 | /* Simple pagination (prev/next only) */ 86 | .pagination-simple { 87 | display: flex; 88 | justify-content: space-between; 89 | align-items: center; 90 | margin: 50px 0; 91 | } 92 | 93 | .pagination-simple .prev, 94 | .pagination-simple .next { 95 | flex: 1; 96 | } 97 | 98 | .pagination-simple .prev { 99 | text-align: left; 100 | } 101 | 102 | .pagination-simple .next { 103 | text-align: right; 104 | } 105 | 106 | /* Responsive */ 107 | @media (max-width: 684px) { 108 | .pagination__buttons { 109 | flex-direction: column; 110 | } 111 | 112 | .button.previous, 113 | .button.next { 114 | margin: 0; 115 | width: 100%; 116 | } 117 | } -------------------------------------------------------------------------------- /src/content/posts/rich-content.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Rich Content' 3 | description: 'Examples of embedded media content' 4 | pubDate: 2019-03-10 5 | author: 'Terminal Theme Authors' 6 | tags: ['media', 'embeds', 'video'] 7 | --- 8 | 9 | This page demonstrates how to embed various types of media content in the Astro Terminal theme, including YouTube videos, Vimeo players, and GitHub code snippets. 10 | 11 | --- 12 | 13 | ## YouTube Video Embed 14 | 15 |
16 | 17 |
18 | 19 |
20 | 21 | --- 22 | 23 | ## Code Snippet from GitHub 24 | 25 | 26 | 27 |
28 | 29 | --- 30 | 31 | ## Vimeo Video Player 32 | 33 |
34 | 35 |
36 | 37 |
38 | 39 | --- 40 | 41 | ## Spotify Music Player 42 | 43 | 44 | 45 |
46 | 47 | --- 48 | 49 | ## CodePen Interactive Demo 50 | 51 | 54 | 55 |
56 | 57 | --- 58 | 59 | ## Interactive Map 60 | 61 | 62 | 63 |
64 | 65 | --- 66 | 67 | ## SoundCloud Audio 68 | 69 | -------------------------------------------------------------------------------- /src/styles/gist.css: -------------------------------------------------------------------------------- 1 | /* GitHub Gist embeds - Dark Theme */ 2 | .gist { 3 | font-size: calc(var(--font-size) * 0.9) !important; 4 | } 5 | 6 | .gist .gist-file { 7 | border: 1px solid var(--accent) !important; 8 | border-radius: var(--radius) !important; 9 | margin-bottom: 1em !important; 10 | } 11 | 12 | /* Dark background for gist data area */ 13 | .gist .gist-data { 14 | background-color: var(--background) !important; 15 | border-bottom: none !important; 16 | } 17 | 18 | /* Override GitHub's table styling */ 19 | .gist table { 20 | background: var(--background) !important; 21 | } 22 | 23 | .gist .highlight { 24 | background: var(--background) !important; 25 | } 26 | 27 | .gist td.blob-num { 28 | background: var(--background) !important; 29 | border-right: 1px solid color-mix(in srgb, var(--foreground) 15%, transparent) !important; 30 | } 31 | 32 | .gist td.blob-code { 33 | background: var(--background) !important; 34 | } 35 | 36 | .gist .gist-meta { 37 | background-color: var(--accent) !important; 38 | color: var(--background) !important; 39 | padding: 10px !important; 40 | } 41 | 42 | .gist .gist-meta a { 43 | color: var(--background) !important; 44 | text-decoration: none !important; 45 | font-weight: 600 !important; 46 | } 47 | 48 | .gist .gist-meta a:hover { 49 | text-decoration: underline !important; 50 | } 51 | 52 | /* Gist line numbers */ 53 | .gist .blob-num { 54 | background: var(--background) !important; 55 | color: color-mix(in srgb, var(--foreground) 50%, transparent) !important; 56 | border: none !important; 57 | } 58 | 59 | /* Gist code */ 60 | .gist .blob-code { 61 | background: var(--background) !important; 62 | color: var(--foreground) !important; 63 | } 64 | 65 | .gist .blob-code-inner { 66 | color: var(--foreground) !important; 67 | } 68 | 69 | /* Gist syntax highlighting - monochrome */ 70 | .gist .pl-c { 71 | /* Comment */ 72 | color: color-mix(in srgb, var(--foreground) 50%, transparent) !important; 73 | font-style: italic !important; 74 | } 75 | 76 | .gist .pl-k, /* Keyword */ 77 | .gist .pl-s, /* String */ 78 | .gist .pl-e, /* Entity */ 79 | .gist .pl-en, /* Entity name */ 80 | .gist .pl-v, /* Variable */ 81 | .gist .pl-bu, /* Built-in */ 82 | .gist .pl-ii, /* Invalid illegal */ 83 | .gist .pl-c1, /* Constant */ 84 | .gist .pl-ml, /* Markup list */ 85 | .gist .pl-mh, /* Markup heading */ 86 | .gist .pl-ms, /* Markup styling */ 87 | .gist .pl-mi, /* Markup italic */ 88 | .gist .pl-mb, /* Markup bold */ 89 | .gist .pl-md, /* Markup deleted */ 90 | .gist .pl-mi1, /* Markup inserted */ 91 | .gist .pl-mc, /* Markup changed */ 92 | .gist .pl-sr, /* String regex */ 93 | .gist .pl-sra, /* String regex anchor */ 94 | .gist .pl-sre { /* String regex escape */ 95 | color: var(--accent) !important; 96 | } 97 | 98 | /* Hide Gist footer */ 99 | .gist .gist-footer { 100 | display: none !important; 101 | } -------------------------------------------------------------------------------- /src/styles/code.css: -------------------------------------------------------------------------------- 1 | /* Code blocks functionality */ 2 | .highlight { 3 | position: relative; 4 | margin: 20px 0; 5 | border: 1px solid var(--code-border); 6 | } 7 | 8 | .code-title { 9 | display: flex; 10 | align-items: center; 11 | justify-content: space-between; 12 | background: color-mix(in srgb, var(--foreground) 5%, transparent); 13 | border-bottom: 1px solid var(--code-border); 14 | color: var(--comment); 15 | text-transform: uppercase; 16 | font-size: calc(var(--font-size) * .8); 17 | padding: 6px 10px; 18 | line-height: 1; 19 | } 20 | 21 | .copy-button { 22 | position: relative; 23 | display: inline-flex; 24 | align-items: center; 25 | justify-content: center; 26 | padding: 3px 8px; 27 | text-decoration: none; 28 | text-align: center; 29 | font-size: 13px; 30 | font-weight: 500; 31 | border: 1px solid color-mix(in srgb, var(--accent) 15%, transparent); 32 | appearance: none; 33 | cursor: pointer; 34 | outline: none; 35 | background: transparent; 36 | color: var(--accent); 37 | transition: all .15s linear; 38 | } 39 | 40 | .copy-button:hover { 41 | background: color-mix(in srgb, var(--accent) 15%, transparent); 42 | } 43 | 44 | /* Collapsible code blocks */ 45 | .collapsible { 46 | position: relative; 47 | margin: 40px 0; 48 | } 49 | 50 | .collapsible-title { 51 | position: relative; 52 | padding: 10px; 53 | background: color-mix(in srgb, var(--foreground) 5%, transparent); 54 | border: 1px solid color-mix(in srgb, var(--foreground) 10%, transparent); 55 | border-radius: var(--radius); 56 | cursor: pointer; 57 | user-select: none; 58 | } 59 | 60 | .collapsible-title::after { 61 | content: '▶'; 62 | position: absolute; 63 | right: 10px; 64 | transition: transform .2s; 65 | } 66 | 67 | .collapsible.open .collapsible-title::after { 68 | transform: rotate(90deg); 69 | } 70 | 71 | .collapsible-content { 72 | display: none; 73 | margin-top: -1px; 74 | border: 1px solid color-mix(in srgb, var(--foreground) 10%, transparent); 75 | border-top: none; 76 | border-radius: 0 0 var(--radius) var(--radius); 77 | } 78 | 79 | .collapsible.open .collapsible-content { 80 | display: block; 81 | } 82 | 83 | /* Line numbers */ 84 | .line-numbers { 85 | position: relative; 86 | padding-left: 3.8em !important; 87 | counter-reset: linenumber; 88 | } 89 | 90 | .line-numbers > code { 91 | position: relative; 92 | white-space: inherit; 93 | } 94 | 95 | .line-numbers .line-numbers-rows { 96 | position: absolute; 97 | pointer-events: none; 98 | top: 0; 99 | font-size: 100%; 100 | left: -3.8em; 101 | width: 3em; 102 | letter-spacing: -1px; 103 | border-right: 1px solid color-mix(in srgb, var(--foreground) 10%, transparent); 104 | user-select: none; 105 | } 106 | 107 | .line-numbers-rows > span { 108 | display: block; 109 | counter-increment: linenumber; 110 | } 111 | 112 | .line-numbers-rows > span:before { 113 | content: counter(linenumber); 114 | color: color-mix(in srgb, var(--foreground) 50%, transparent); 115 | display: block; 116 | padding-right: 0.8em; 117 | text-align: right; 118 | } -------------------------------------------------------------------------------- /src/styles/menu.css: -------------------------------------------------------------------------------- 1 | /* Navigation */ 2 | .navigation-menu { 3 | display: flex; 4 | align-items: flex-start; 5 | justify-content: space-between; 6 | margin: 20px 1px; 7 | } 8 | 9 | .navigation-menu__inner { 10 | display: flex; 11 | flex: 1; 12 | flex-wrap: wrap; 13 | list-style: none; 14 | margin: 0; 15 | padding: 0; 16 | } 17 | 18 | .navigation-menu__inner > li { 19 | flex: none; 20 | margin-bottom: 10px; 21 | white-space: nowrap; 22 | } 23 | 24 | .navigation-menu__inner > li:not(:last-of-type) { 25 | margin-right: 20px; 26 | } 27 | 28 | /* Menu system */ 29 | .menu { 30 | display: flex; 31 | flex-direction: column; 32 | position: relative; 33 | list-style: none; 34 | padding: 0; 35 | margin: 0; 36 | } 37 | 38 | /* Remove list styling from all menu ul elements */ 39 | .menu ul, 40 | .navigation-menu ul, 41 | .header ul { 42 | list-style: none; 43 | margin: 0; 44 | padding: 0; 45 | } 46 | 47 | /* Remove dash before menu items */ 48 | .menu li::before, 49 | .navigation-menu li::before, 50 | .header li::before, 51 | .menu__dropdown li::before, 52 | .navigation-menu__inner li::before { 53 | content: none !important; 54 | } 55 | 56 | .menu__trigger { 57 | margin-right: 0 !important; 58 | color: var(--accent); 59 | user-select: none; 60 | cursor: pointer; 61 | } 62 | 63 | .menu__dropdown { 64 | display: none; 65 | flex-direction: column; 66 | position: absolute; 67 | background: var(--background); 68 | box-shadow: 0 10px var(--background), -10px 10px var(--background), 10px 10px var(--background); 69 | color: var(--accent); 70 | border: 2px solid var(--accent); 71 | margin: 0; 72 | padding: 10px; 73 | top: 10px; 74 | left: 0; 75 | list-style: none; 76 | z-index: 99; 77 | } 78 | 79 | .open .menu__dropdown { 80 | display: flex; 81 | } 82 | 83 | .menu__dropdown > li:not(:last-of-type) { 84 | margin-bottom: 10px; 85 | } 86 | 87 | .menu__dropdown > li > a { 88 | display: flex; 89 | padding: 5px; 90 | } 91 | 92 | /* Mobile menu */ 93 | .menu--mobile { 94 | display: none; 95 | list-style: none; 96 | margin: 0; 97 | padding: 0; 98 | flex: 0 0 auto; 99 | } 100 | 101 | .menu--mobile .menu__trigger { 102 | color: var(--accent); 103 | border: 2px solid; 104 | margin-left: 0; 105 | height: 100%; 106 | padding: 3px 8px; 107 | margin-bottom: 0 !important; 108 | position: relative; 109 | cursor: pointer; 110 | } 111 | 112 | /* Language selector */ 113 | .menu--language-selector .menu__trigger { 114 | color: var(--accent); 115 | border: 2px solid; 116 | margin-left: 10px; 117 | padding: 3px 8px; 118 | } 119 | 120 | /* Responsive */ 121 | @media (max-width: 684px) { 122 | .navigation-menu { 123 | margin: 0; 124 | } 125 | 126 | .menu--desktop { 127 | display: none; 128 | } 129 | 130 | .menu--mobile { 131 | display: flex; 132 | } 133 | 134 | .menu--mobile .menu__dropdown { 135 | left: auto; 136 | right: 0; 137 | top: 100%; 138 | margin-top: 5px; 139 | } 140 | 141 | .menu--language-selector .menu__dropdown { 142 | left: auto; 143 | right: 0; 144 | } 145 | } -------------------------------------------------------------------------------- /src/layouts/PostLayout.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import BaseLayout from './BaseLayout.astro'; 3 | import FormattedDate from '../components/FormattedDate.astro'; 4 | 5 | export interface Props { 6 | title: string; 7 | description?: string; 8 | pubDate: Date; 9 | updatedDate?: Date; 10 | author?: string; 11 | image?: string; 12 | tags?: string[]; 13 | } 14 | 15 | const { title, description, pubDate, updatedDate, author, image, tags = [] } = Astro.props; 16 | const base = import.meta.env.BASE_URL.endsWith('/') ? import.meta.env.BASE_URL : import.meta.env.BASE_URL + '/'; 17 | --- 18 | 19 | 20 |
21 |

{title}

22 | 23 | 43 | 44 | {image && ( 45 |
46 | {title} 47 |
48 | )} 49 | 50 |
51 | 52 |
53 |
54 | 55 | 97 | 98 | 99 | 100 |
-------------------------------------------------------------------------------- /src/styles/terms.css: -------------------------------------------------------------------------------- 1 | /* Terms/Taxonomy pages */ 2 | .terms { 3 | display: flex; 4 | flex-direction: column; 5 | gap: 20px; 6 | max-width: 100%; 7 | } 8 | 9 | .terms h3 { 10 | font-size: calc(var(--font-size) * 1.2); 11 | } 12 | 13 | .terms__list { 14 | display: flex; 15 | flex-flow: row wrap; 16 | gap: 10px; 17 | list-style: none; 18 | padding: 0; 19 | margin: 0; 20 | } 21 | 22 | .terms__item { 23 | display: flex; 24 | flex-direction: column; 25 | align-items: center; 26 | justify-content: center; 27 | min-width: 150px; 28 | padding: 10px 20px; 29 | background: color-mix(in srgb, var(--foreground) 5%, transparent); 30 | border: 1px solid color-mix(in srgb, var(--foreground) 10%, transparent); 31 | border-radius: var(--radius); 32 | transition: all 0.15s linear; 33 | } 34 | 35 | .terms__item:hover { 36 | background: color-mix(in srgb, var(--accent) 15%, transparent); 37 | border-color: var(--accent); 38 | } 39 | 40 | .terms__item a { 41 | color: var(--accent); 42 | text-decoration: none; 43 | font-weight: 700; 44 | } 45 | 46 | .terms__item .count { 47 | color: color-mix(in srgb, var(--foreground) 65%, transparent); 48 | font-size: calc(var(--font-size) * 0.9); 49 | } 50 | 51 | /* Term cloud */ 52 | .terms-cloud { 53 | display: flex; 54 | flex-wrap: wrap; 55 | gap: 15px; 56 | align-items: baseline; 57 | justify-content: center; 58 | margin: 40px 0; 59 | } 60 | 61 | .terms-cloud a { 62 | text-decoration: none; 63 | color: var(--accent); 64 | transition: all 0.15s linear; 65 | } 66 | 67 | .terms-cloud a:hover { 68 | color: var(--foreground); 69 | text-decoration: underline; 70 | } 71 | 72 | /* Size variations for tag cloud */ 73 | .terms-cloud .size-1 { font-size: calc(var(--font-size) * 0.8); } 74 | .terms-cloud .size-2 { font-size: calc(var(--font-size) * 0.9); } 75 | .terms-cloud .size-3 { font-size: calc(var(--font-size) * 1); } 76 | .terms-cloud .size-4 { font-size: calc(var(--font-size) * 1.1); } 77 | .terms-cloud .size-5 { font-size: calc(var(--font-size) * 1.2); } 78 | .terms-cloud .size-6 { font-size: calc(var(--font-size) * 1.3); } 79 | 80 | /* Single term page */ 81 | .term-posts { 82 | margin-top: 40px; 83 | } 84 | 85 | .term-posts h2 { 86 | color: var(--accent); 87 | border-bottom: 3px dotted var(--accent); 88 | padding-bottom: 10px; 89 | } 90 | 91 | /* Archives by date */ 92 | .archives { 93 | list-style: none; 94 | padding: 0; 95 | margin: 0; 96 | } 97 | 98 | .archives__year { 99 | margin-top: 40px; 100 | } 101 | 102 | .archives__year:first-child { 103 | margin-top: 0; 104 | } 105 | 106 | .archives__year-header { 107 | font-size: calc(var(--font-size) * 1.2); 108 | color: var(--accent); 109 | margin-bottom: 20px; 110 | } 111 | 112 | .archives__month { 113 | margin-left: 20px; 114 | margin-bottom: 20px; 115 | } 116 | 117 | .archives__month-header { 118 | font-weight: 700; 119 | margin-bottom: 10px; 120 | } 121 | 122 | .archives__posts { 123 | list-style: none; 124 | padding: 0; 125 | margin-left: 20px; 126 | } 127 | 128 | .archives__post { 129 | display: flex; 130 | align-items: baseline; 131 | margin-bottom: 10px; 132 | } 133 | 134 | .archives__post-date { 135 | flex-shrink: 0; 136 | color: color-mix(in srgb, var(--foreground) 65%, transparent); 137 | margin-right: 10px; 138 | } 139 | 140 | .archives__post-title { 141 | flex: 1; 142 | } 143 | 144 | .archives__post-title a { 145 | color: var(--foreground); 146 | text-decoration: none; 147 | } 148 | 149 | .archives__post-title a:hover { 150 | color: var(--accent); 151 | text-decoration: underline; 152 | } -------------------------------------------------------------------------------- /src/styles/main.css: -------------------------------------------------------------------------------- 1 | /* Container */ 2 | .container { 3 | display: flex; 4 | flex-direction: column; 5 | padding: 40px; 6 | max-width: 864px; 7 | min-height: 100vh; 8 | margin: 0 auto; 9 | border-right: 1px solid color-mix(in srgb, var(--accent) 10%, transparent); 10 | } 11 | 12 | .container.full, 13 | .container.center { 14 | border: none; 15 | margin: 0 auto; 16 | } 17 | 18 | .container.full { 19 | max-width: 100%; 20 | } 21 | 22 | .content { 23 | display: flex; 24 | flex-direction: column; 25 | flex: 1; 26 | } 27 | 28 | /* Index content */ 29 | .index-content { 30 | margin-bottom: 40px; 31 | } 32 | 33 | .index-content.framed { 34 | margin-bottom: 60px; 35 | } 36 | 37 | /* Framed content */ 38 | .framed { 39 | border: 3px solid var(--accent); 40 | padding: 20px; 41 | } 42 | 43 | /* Figure with caption - matching Hugo theme */ 44 | figure { 45 | display: inline-block; 46 | margin: 25px auto; 47 | padding: 0; 48 | background: transparent; 49 | text-align: center; 50 | width: fit-content; 51 | } 52 | 53 | figure img { 54 | display: block; 55 | margin-bottom: -8px !important; 56 | /* Image already has border from terminal.css */ 57 | } 58 | 59 | /* Override figcaption from terminal.css to match Hugo theme */ 60 | figure figcaption { 61 | margin-top: 0 !important; 62 | padding: 15px 20px !important; 63 | background: var(--accent) !important; 64 | color: var(--background) !important; 65 | font-weight: normal !important; 66 | text-align: center !important; 67 | /* Thicker bottom border effect */ 68 | border: none !important; 69 | border-radius: 0 !important; 70 | position: relative; 71 | } 72 | 73 | figure figcaption p { 74 | margin: 0; 75 | } 76 | 77 | /* Page styles */ 78 | .page { 79 | max-width: 100%; 80 | margin: 0 auto; 81 | } 82 | 83 | .page h1, 84 | article h1 { 85 | position: relative; 86 | color: var(--accent); 87 | margin-top: 0 !important; 88 | margin-bottom: 15px !important; 89 | padding-bottom: 15px; 90 | border-bottom: 3px dotted var(--accent); 91 | } 92 | 93 | .page h1::after, 94 | article h1::after { 95 | content: ""; 96 | position: absolute; 97 | bottom: 2px; 98 | display: block; 99 | width: 100%; 100 | border-bottom: 3px dotted var(--accent); 101 | } 102 | 103 | /* Responsive */ 104 | @media (max-width: 684px) { 105 | .container { 106 | padding: 20px; 107 | } 108 | 109 | .content { 110 | margin-top: 20px; 111 | } 112 | } 113 | 114 | /* Print styles */ 115 | @media print { 116 | .container { 117 | display: initial; 118 | } 119 | 120 | .content { 121 | display: initial; 122 | } 123 | } 124 | 125 | /* Utility classes */ 126 | .text-center { 127 | text-align: center; 128 | } 129 | 130 | .text-left { 131 | text-align: left; 132 | } 133 | 134 | .text-right { 135 | text-align: right; 136 | } 137 | 138 | .float-left { 139 | float: left; 140 | } 141 | 142 | .float-right { 143 | float: right; 144 | } 145 | 146 | .mb-0 { 147 | margin-bottom: 0 !important; 148 | } 149 | 150 | .mt-0 { 151 | margin-top: 0 !important; 152 | } 153 | 154 | /* Clearfix */ 155 | .clearfix::after { 156 | content: ""; 157 | display: table; 158 | clear: both; 159 | } 160 | 161 | /* Lists with dashes */ 162 | ul { 163 | list-style: none; 164 | } 165 | 166 | ul li { 167 | position: relative; 168 | } 169 | 170 | ul li:not(:empty)::before { 171 | content: "-"; 172 | position: absolute; 173 | left: -20px; 174 | color: var(--accent); 175 | } 176 | 177 | /* Nested lists */ 178 | ul ul { 179 | margin-left: 20px; 180 | } 181 | 182 | /* Selection */ 183 | ::selection { 184 | background: var(--accent); 185 | color: var(--background); 186 | } 187 | 188 | ::-moz-selection { 189 | background: var(--accent); 190 | color: var(--background); 191 | } -------------------------------------------------------------------------------- /src/content/posts/showcase.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Showcase' 3 | description: 'This article offers a sample of basic Markdown syntax that can be used in Astro content files, also it shows whether basic HTML elements are decorated with CSS in the Astro Terminal theme.' 4 | pubDate: 2019-03-11 5 | author: 'Hugo Authors, Radek' 6 | tags: [] 7 | --- 8 | 9 | This article offers a sample of basic Markdown syntax that can be used in Astro content files, also it shows whether basic HTML elements are decorated with CSS in the Astro Terminal theme. 10 | 11 | ## Headings 12 | 13 | The following HTML `

`—`

` elements represent six levels of section headings. `

` is the highest section level while `

` is the lowest. 14 | 15 | # H1 16 | ## H2 17 | ### H3 18 | #### H4 19 | ##### H5 20 | ###### H6 21 | 22 | ## Paragraph 23 | 24 | Xerum, quo qui aut unt expliquam qui dolut labo. Aque venitatiusda cum, voluptionse latur sitiae dolessi aut parist aut dollo enim qui voluptate ma dolestendit peritin re plis aut quas inctum laceat est volestemque commosa as cus endigna tectur, offic to cor sequas etum rerum idem sintibus eiur? Quianimin porecus evelectur, cum que nis nust voloribus ratem aut omnimi, sitatur? Quiatem. Nam, omnis sum am facea corem alique molestrunt et eos evelece arcillit ut aut eos eos nus, sin conecerem erum fuga. Ri oditatquam, ad quibus unda veliamenimin cusam et facea ipsamus es exerum sitate dolores editium rerore eost, temped molorro ratiae volorro te reribus dolorer sperchicium faceata tiustia prat. 25 | 26 | Itatur? Quiatae cullecum rem ent aut odis in re eossequodi nonsequ idebis ne sapicia is sinveli squiatum, core et que aut hariosam ex eat. 27 | 28 | ## Images 29 | 30 | ![Terminal Theme Preview](https://raw.githubusercontent.com/panr/hugo-theme-terminal/refs/heads/master/images/terminal-theme.png?raw=true) 31 | 32 | ### Figure with a caption 33 | 34 |
35 | Terminal Theme Preview 36 |
37 |

Terminal Theme Preview

38 |
39 |
40 | 41 | ## Blockquotes 42 | 43 | The blockquote element represents content that is quoted from another source, optionally with a citation which must be within a `footer` or `cite` element, and optionally with in-line changes such as annotations and abbreviations. 44 | 45 | ### Blockquote without attribution 46 | 47 | > Tiam, ad mint andaepu dandae nostion secatur sequo quae. 48 | > **Note** that you can use *Markdown syntax* within a blockquote. 49 | 50 | ### Blockquote with attribution 51 | 52 | > Don't communicate by sharing memory, share memory by communicating. 53 | > — Rob Pike[^1] 54 | 55 | [^1]: The above quote is excerpted from Rob Pike's [talk](https://www.youtube.com/watch?v=PAAkCSZUG1c) during Gopherfest, November 18, 2015. 56 | 57 | ## Buttons and links 58 | 59 | 60 | Link 61 | 62 | ## Tables 63 | 64 | Tables aren't part of the core Markdown spec, but Astro supports them out-of-the-box. 65 | 66 | ### Basic Table 67 | 68 | | Name | Age | 69 | | ---- | --- | 70 | | Bob | 27 | 71 | | Alice | 23 | 72 | 73 | ### Inline Markdown within tables 74 | 75 | | Italics | Bold | Code | 76 | | ------- | ---- | ---- | 77 | | *italics* | **bold** | `code` | 78 | 79 | ## Forms 80 | 81 |
82 |
83 |
84 |
85 |
90 |
91 | 94 | 95 |
96 | 97 | ## Code Blocks 98 | 99 | ### Code block with backticks 100 | 101 | ```html 102 | 103 | 104 | 105 | 106 | Example HTML5 Document 107 | 108 | 109 |

Test

110 | 111 | 112 | ``` 113 | 114 | For more examples for different programming languages, please go to [code showcase](/blog/code-blocks-examples/). 115 | 116 | ## List Types 117 | 118 | ### Ordered List 119 | 120 | 1. First item 121 | 2. Second item 122 | 3. Third item 123 | 124 | ### Unordered List 125 | 126 | * List item 127 | * Another item 128 | * And another item 129 | 130 | ### Nested list 131 | 132 | * Fruit 133 | * Apple 134 | * Orange 135 | * Banana 136 | * Dairy 137 | * Milk 138 | * Cheese 139 | 140 | ## Other Elements — abbr, sub, sup, kbd, mark 141 | 142 | GIF is a bitmap image format. 143 | 144 | H2O 145 | 146 | Xn + Yn = Zn 147 | 148 | Press CTRL+ALT+Delete to end the session. 149 | 150 | Most salamanders are nocturnal, and hunt for insects, worms, and other small creatures. -------------------------------------------------------------------------------- /src/styles/syntax.css: -------------------------------------------------------------------------------- 1 | /* Syntax highlighting theme for Astro Terminal theme */ 2 | /* This uses a monochrome approach where all syntax elements are in accent color */ 3 | 4 | /* Astro/Shiki CSS Variables for monochrome syntax highlighting */ 5 | :root { 6 | /* Base colors - most text should be white/foreground */ 7 | --astro-code-color-text: var(--foreground); 8 | --astro-code-color-background: color-mix(in srgb, var(--foreground) 5%, transparent); 9 | 10 | /* Default all tokens to foreground (white) */ 11 | --astro-code-foreground: var(--foreground); 12 | --astro-code-token-constant: var(--foreground); 13 | --astro-code-token-string: var(--foreground); 14 | --astro-code-token-comment: color-mix(in srgb, var(--foreground) 50%, transparent); 15 | --astro-code-token-keyword: var(--accent); 16 | --astro-code-token-parameter: var(--foreground); 17 | --astro-code-token-function: color-mix(in srgb, var(--accent) 70%, transparent); 18 | --astro-code-token-string-expression: var(--accent); 19 | --astro-code-token-punctuation: var(--foreground); 20 | --astro-code-token-link: var(--accent); 21 | 22 | /* Additional token types */ 23 | --astro-code-token-namespace: var(--foreground); 24 | --astro-code-token-tag: var(--accent); 25 | --astro-code-token-selector: var(--accent); 26 | --astro-code-token-attribute: var(--foreground); 27 | --astro-code-token-attribute-name: color-mix(in srgb, var(--accent) 70%, transparent); 28 | --astro-code-token-variable: var(--foreground); 29 | --astro-code-token-literal: var(--foreground); 30 | --astro-code-token-number: var(--foreground); 31 | --astro-code-token-unit: var(--foreground); 32 | --astro-code-token-symbol: var(--foreground); 33 | --astro-code-token-regex: var(--accent); 34 | --astro-code-token-boolean: var(--accent); 35 | --astro-code-token-important: var(--accent); 36 | 37 | /* Additional variables for code styling */ 38 | --code-border: color-mix(in srgb, var(--foreground) 25%, transparent); 39 | --comment: color-mix(in srgb, var(--foreground) 50%, transparent); 40 | } 41 | 42 | /* Override Astro's default code block styles */ 43 | .astro-code { 44 | background: color-mix(in srgb, var(--foreground) 5%, transparent) !important; 45 | border: 1px solid color-mix(in srgb, var(--foreground) 25%, transparent) !important; 46 | padding: 20px 10px !important; 47 | } 48 | 49 | /* Override inline styles for specific token types */ 50 | 51 | /* Force all text to be white by default */ 52 | .astro-code, 53 | .astro-code span { 54 | color: var(--foreground) !important; 55 | } 56 | 57 | /* Default foreground/white text */ 58 | .astro-code span[style*="--astro-code-foreground"], 59 | .astro-code span[style*="--astro-code-token-punctuation"], 60 | .astro-code span[style*="--astro-code-token-constant"], 61 | .astro-code span[style*="--astro-code-token-string"], 62 | .astro-code span[style*="--astro-code-token-parameter"], 63 | .astro-code span[style*="--astro-code-token-variable"], 64 | .astro-code span[style*="--astro-code-token-literal"], 65 | .astro-code span[style*="--astro-code-token-number"] { 66 | color: var(--foreground) !important; 67 | } 68 | 69 | /* Full orange for keywords, tags, string expressions */ 70 | .astro-code span[style*="--astro-code-token-keyword"], 71 | .astro-code span[style*="--astro-code-token-string-expression"], 72 | .astro-code span[style*="--astro-code-token-tag"], 73 | .astro-code span[style*="--astro-code-token-selector"], 74 | .astro-code span[style*="--astro-code-token-regex"], 75 | .astro-code span[style*="--astro-code-token-boolean"], 76 | .astro-code span[style*="--astro-code-token-important"] { 77 | color: var(--accent) !important; 78 | } 79 | 80 | /* 70% opacity orange for attribute names (using token-function in HTML) */ 81 | .astro-code span[style*="--astro-code-token-function"] { 82 | color: var(--accent) !important; 83 | opacity: 0.7; 84 | } 85 | 86 | /* Muted comments */ 87 | .astro-code span[style*="--astro-code-token-comment"] { 88 | color: var(--comment) !important; 89 | } 90 | 91 | /* Line highlighting */ 92 | .highlight-line { 93 | display: block; 94 | margin: 0 -10px; 95 | padding: 0 10px; 96 | background: color-mix(in srgb, var(--accent) 10%, transparent); 97 | border-left: 3px solid var(--accent); 98 | } 99 | 100 | /* Diff highlighting */ 101 | .token.deleted { 102 | color: #ff6666 !important; 103 | background: color-mix(in srgb, #ff6666 10%, transparent); 104 | } 105 | 106 | .token.inserted { 107 | color: #66ff66 !important; 108 | background: color-mix(in srgb, #66ff66 10%, transparent); 109 | } 110 | 111 | /* Inline code in different contexts */ 112 | .post-content code, 113 | .page code { 114 | word-break: break-word; 115 | } 116 | 117 | /* Code block containers */ 118 | .code-toolbar { 119 | position: relative; 120 | } 121 | 122 | .code-toolbar .toolbar { 123 | position: absolute; 124 | top: 10px; 125 | right: 10px; 126 | display: flex; 127 | gap: 5px; 128 | } 129 | 130 | .code-toolbar .toolbar-item button { 131 | background: transparent; 132 | border: 1px solid var(--accent); 133 | color: var(--accent); 134 | padding: 2px 8px; 135 | font-size: calc(var(--font-size) * 0.8); 136 | cursor: pointer; 137 | transition: all 0.15s linear; 138 | } 139 | 140 | .code-toolbar .toolbar-item button:hover { 141 | background: color-mix(in srgb, var(--accent) 15%, transparent); 142 | } -------------------------------------------------------------------------------- /src/styles/post.css: -------------------------------------------------------------------------------- 1 | /* Posts */ 2 | .posts { 3 | width: 100%; 4 | } 5 | 6 | .post { 7 | width: 100%; 8 | text-align: left; 9 | padding: 30px 0; 10 | } 11 | 12 | .post:not(:last-of-type) { 13 | border-bottom: 1px solid color-mix(in srgb, var(--foreground) 10%, transparent); 14 | } 15 | 16 | .post-meta { 17 | font-size: inherit; 18 | margin-bottom: 10px; 19 | color: color-mix(in srgb, var(--foreground) 65%, transparent); 20 | } 21 | 22 | .post-author::before { 23 | content: " :: "; 24 | display: inline; 25 | } 26 | 27 | .post-title { 28 | position: relative; 29 | color: var(--accent); 30 | margin-top: 0 !important; 31 | margin-bottom: 15px !important; 32 | padding-bottom: 15px; 33 | border-bottom: 3px dotted var(--accent); 34 | text-decoration: none !important; 35 | } 36 | 37 | .post-title::after { 38 | content: ""; 39 | position: absolute; 40 | bottom: 2px; 41 | display: block; 42 | width: 100%; 43 | border-bottom: 3px dotted var(--accent); 44 | } 45 | 46 | .post-title a { 47 | text-decoration: none; 48 | } 49 | 50 | .post-tags { 51 | display: block; 52 | margin-bottom: 20px; 53 | font-size: inherit; 54 | color: var(--accent); 55 | } 56 | 57 | .post-tags a { 58 | color: var(--accent); 59 | text-decoration: none; 60 | } 61 | 62 | .post-tags a::before { 63 | content: "#"; 64 | } 65 | 66 | .post-tags a:not(:last-child)::after { 67 | content: ", "; 68 | } 69 | 70 | .post-cover { 71 | margin: 20px 0; 72 | } 73 | 74 | .post-cover img { 75 | border: 10px solid var(--accent); 76 | background: transparent; 77 | padding: 20px; 78 | } 79 | 80 | .post ul { 81 | list-style: none; 82 | } 83 | 84 | .post ul li { 85 | position: relative; 86 | } 87 | 88 | .post ul li:not(:empty)::before { 89 | content: "-"; 90 | position: absolute; 91 | left: -20px; 92 | color: var(--accent); 93 | } 94 | 95 | .post-content { 96 | margin-top: 30px; 97 | } 98 | 99 | /* Post navigation */ 100 | .post-nav { 101 | display: flex; 102 | justify-content: space-between; 103 | margin-top: 100px; 104 | padding-top: 25px; 105 | border-top: 1px solid color-mix(in srgb, var(--foreground) 10%, transparent); 106 | } 107 | 108 | .post-nav-prev, 109 | .post-nav-next { 110 | display: flex; 111 | flex-direction: column; 112 | flex: 1; 113 | } 114 | 115 | .post-nav-next { 116 | text-align: right; 117 | } 118 | 119 | /* Metadata in single posts */ 120 | article time { 121 | color: color-mix(in srgb, var(--foreground) 65%, transparent); 122 | } 123 | 124 | /* Removed - handled by .post-author::before instead */ 125 | 126 | /* Video embeds */ 127 | .video-wrapper { 128 | position: relative; 129 | padding-bottom: 56.25%; /* 16:9 aspect ratio */ 130 | height: 0; 131 | overflow: hidden; 132 | margin: 25px 0; 133 | border: 8px solid var(--accent); 134 | background: var(--background); 135 | } 136 | 137 | .video-wrapper iframe { 138 | position: absolute; 139 | top: 0; 140 | left: 0; 141 | width: 100%; 142 | height: 100%; 143 | border: none; 144 | } 145 | 146 | /* Twitter embeds */ 147 | .twitter-tweet { 148 | margin: 25px auto !important; 149 | max-width: 550px !important; 150 | } 151 | 152 | /* Spotify embed */ 153 | iframe[src*="spotify"] { 154 | border: 8px solid var(--accent) !important; 155 | border-radius: 0 !important; 156 | margin: 25px 0; 157 | } 158 | 159 | /* Google Maps */ 160 | iframe[src*="google.com/maps"] { 161 | border: 8px solid var(--accent); 162 | margin: 25px 0; 163 | } 164 | 165 | /* SoundCloud */ 166 | iframe[src*="soundcloud"] { 167 | border: 8px solid var(--accent); 168 | margin: 25px 0; 169 | } 170 | 171 | 172 | /* CodePen embeds */ 173 | iframe[src*="codepen"] { 174 | border: 8px solid var(--accent); 175 | margin: 25px 0; 176 | } 177 | 178 | /* Posts list */ 179 | .posts-list { 180 | list-style: none; 181 | padding: 0; 182 | margin: 0; 183 | } 184 | 185 | .posts-list li { 186 | margin-bottom: 30px; 187 | } 188 | 189 | /* List page title */ 190 | .posts-title { 191 | position: relative; 192 | color: var(--accent); 193 | margin-top: 0 !important; 194 | margin-bottom: 0 !important; 195 | padding-bottom: 15px; 196 | border-bottom: 3px dotted var(--accent); 197 | } 198 | 199 | .posts-title::after { 200 | content: ""; 201 | position: absolute; 202 | bottom: 2px; 203 | display: block; 204 | width: 100%; 205 | border-bottom: 3px dotted var(--accent); 206 | } 207 | 208 | /* Footnotes */ 209 | .footnotes { 210 | margin-top: 50px; 211 | padding-top: 30px; 212 | border-top: 2px solid var(--accent); 213 | } 214 | 215 | .footnotes h2 { 216 | /* Hide the screen reader only heading visually */ 217 | position: absolute; 218 | width: 1px; 219 | height: 1px; 220 | padding: 0; 221 | margin: -1px; 222 | overflow: hidden; 223 | clip: rect(0, 0, 0, 0); 224 | white-space: nowrap; 225 | border: 0; 226 | } 227 | 228 | .footnotes ol { 229 | margin-left: 0; 230 | padding-left: 3ch; 231 | list-style-type: decimal; 232 | } 233 | 234 | .footnotes li { 235 | margin-bottom: 10px; 236 | color: var(--foreground); 237 | } 238 | 239 | .footnotes li::marker { 240 | color: var(--accent); 241 | font-weight: 600; 242 | } 243 | 244 | .footnotes li p { 245 | margin: 0; 246 | display: inline; 247 | } 248 | 249 | /* Footnote references in text */ 250 | sup[data-footnote-ref] { 251 | font-weight: 600; 252 | } 253 | 254 | sup[data-footnote-ref] a { 255 | text-decoration: none; 256 | color: var(--accent); 257 | padding: 0 2px; 258 | } 259 | 260 | sup[data-footnote-ref] a:hover { 261 | background: color-mix(in srgb, var(--accent) 20%, transparent); 262 | } 263 | 264 | /* Footnote back references */ 265 | .data-footnote-backref { 266 | text-decoration: none; 267 | margin-left: 5px; 268 | font-size: 0.9em; 269 | } -------------------------------------------------------------------------------- /src/content/posts/code-blocks-examples.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Code blocks examples' 3 | description: 'The Astro Terminal theme uses Shiki as syntax highlighter, providing beautiful and customizable code highlighting.' 4 | pubDate: 2019-03-10 5 | author: 'Radek' 6 | tags: [] 7 | --- 8 | 9 | The Astro Terminal theme uses Shiki as syntax highlighter, providing beautiful and customizable code highlighting with a monochrome theme that matches the terminal aesthetic. 10 | 11 | Below you can see many basic presentations of the code blocks you can use depending on your needs. 12 | 13 | ## Examples 14 | 15 | ### Raw block with no specified language (and no syntax highlighting): 16 | 17 | ``` 18 | 19 | 20 | 21 | 22 | Example HTML5 Document 23 | 24 | 25 |

Test

26 | 27 | 28 | ``` 29 | 30 | ### With specified language: 31 | 32 | ```html 33 | 34 | 35 | 36 | 37 | Example HTML5 Document 38 | 39 | 40 |

Test

41 | 42 | 43 | ``` 44 | 45 | ## Programming Languages 46 | 47 | ### A 48 | 49 | **Astro:** 50 | ```astro 51 | --- 52 | const greeting = "Hello, World!"; 53 | --- 54 |

{greeting}

55 | ``` 56 | 57 | ### B 58 | 59 | **Bash:** 60 | ```bash 61 | echo "Hello, World!" 62 | ``` 63 | 64 | 65 | ### C 66 | 67 | **C:** 68 | ```c 69 | #include 70 | int main() { 71 | printf("Hello, World!\n"); 72 | return 0; 73 | } 74 | ``` 75 | 76 | **C#:** 77 | ```csharp 78 | using System; 79 | class Program { 80 | static void Main() { 81 | Console.WriteLine("Hello, World!"); 82 | } 83 | } 84 | ``` 85 | 86 | **C++:** 87 | ```cpp 88 | #include 89 | int main() { 90 | std::cout << "Hello, World!" << std::endl; 91 | return 0; 92 | } 93 | ``` 94 | 95 | ### D 96 | 97 | **Docker:** 98 | ```dockerfile 99 | FROM node:18-alpine 100 | WORKDIR /app 101 | COPY . . 102 | RUN npm install 103 | CMD ["npm", "start"] 104 | ``` 105 | 106 | ### E 107 | 108 | **Elixir:** 109 | ```elixir 110 | IO.puts "Hello, World!" 111 | ``` 112 | 113 | **Erlang:** 114 | ```erlang 115 | -module(hello). 116 | -export([world/0]). 117 | world() -> io:format("Hello, World!~n"). 118 | ``` 119 | 120 | ### F 121 | 122 | **F#:** 123 | ```fsharp 124 | printfn "Hello, World!" 125 | ``` 126 | 127 | ### G 128 | 129 | **Go:** 130 | ```go 131 | package main 132 | import "fmt" 133 | func main() { 134 | fmt.Println("Hello, World!") 135 | } 136 | ``` 137 | 138 | **GraphQL:** 139 | ```graphql 140 | type Query { 141 | hello: String! 142 | } 143 | 144 | query GetGreeting { 145 | hello 146 | } 147 | ``` 148 | 149 | ### H 150 | 151 | **Haskell:** 152 | ```haskell 153 | main = putStrLn "Hello, World!" 154 | ``` 155 | 156 | ### J 157 | 158 | **JavaScript:** 159 | ```js 160 | var x, y, z; // Declare 3 variables 161 | x = 5; // Assign the value 5 to x 162 | y = 6; // Assign the value 6 to y 163 | z = x + y; // Assign the sum of x and y to z 164 | 165 | document.getElementById("demo").innerHTML = "The value of z is " + z + "."; 166 | ``` 167 | 168 | **JSX:** 169 | ```jsx 170 | function Video({ video }) { 171 | return ( 172 |
173 | 174 | 175 |

{video.title}

176 |

{video.description}

177 |
178 | 179 |
180 | ); 181 | } 182 | ``` 183 | 184 | **Java:** 185 | ```java 186 | public class HelloWorld { 187 | public static void main(String[] args) { 188 | System.out.println("Hello, World!"); 189 | } 190 | } 191 | ``` 192 | 193 | **JSON:** 194 | ```json 195 | { 196 | "message": "Hello, World!", 197 | "author": "Example", 198 | "version": 1.0 199 | } 200 | ``` 201 | 202 | ### K 203 | 204 | **Kotlin:** 205 | ```kotlin 206 | fun main() { 207 | println("Hello, World!") 208 | } 209 | ``` 210 | 211 | ### L 212 | 213 | **Lua:** 214 | ```lua 215 | print("Hello, World!") 216 | ``` 217 | 218 | ### M 219 | 220 | **Markdown:** 221 | ```markdown 222 | # Hello, World! 223 | 224 | This is a **markdown** example with: 225 | - Lists 226 | - **Bold** and *italic* text 227 | - [Links](https://example.com) 228 | ``` 229 | 230 | **MATLAB:** 231 | ```matlab 232 | disp('Hello, World!') 233 | ``` 234 | 235 | ### N 236 | 237 | **Nim:** 238 | ```nim 239 | echo "Hello, World!" 240 | ``` 241 | 242 | ### O 243 | 244 | **Objective-C:** 245 | ```objective-c 246 | #import 247 | int main() { 248 | @autoreleasepool { 249 | NSLog(@"Hello, World!"); 250 | } 251 | return 0; 252 | } 253 | ``` 254 | 255 | ### P 256 | 257 | **Perl:** 258 | ```perl 259 | print("Hello, World!\n"); 260 | ``` 261 | 262 | **PHP:** 263 | ```php 264 | 265 | ``` 266 | 267 | **Python:** 268 | ```python 269 | print("Hello, World!") 270 | ``` 271 | 272 | ### R 273 | 274 | **R:** 275 | ```r 276 | cat("Hello, World!\n") 277 | ``` 278 | 279 | **Ruby:** 280 | ```ruby 281 | puts "Hello, World!" 282 | ``` 283 | 284 | **Rust:** 285 | ```rust 286 | fn main() { 287 | println!("Hello, World!"); 288 | } 289 | ``` 290 | 291 | ### S 292 | 293 | **Scala:** 294 | ```scala 295 | object HelloWorld extends App { 296 | println("Hello, World!") 297 | } 298 | ``` 299 | 300 | **SQL:** 301 | ```sql 302 | SELECT 'Hello, World!' AS greeting; 303 | 304 | CREATE TABLE messages ( 305 | id INT PRIMARY KEY, 306 | text VARCHAR(255) 307 | ); 308 | 309 | INSERT INTO messages (id, text) VALUES (1, 'Hello, World!'); 310 | ``` 311 | 312 | **Svelte:** 313 | ```svelte 314 | 317 | 318 |

{greeting}

319 | 320 | 325 | ``` 326 | 327 | ### T 328 | 329 | **TOML:** 330 | ```toml 331 | [package] 332 | name = "hello-world" 333 | version = "1.0.0" 334 | 335 | [dependencies] 336 | astro = "^4.0.0" 337 | ``` 338 | 339 | **TypeScript:** 340 | ```typescript 341 | console.log("Hello, World!"); 342 | ``` 343 | 344 | ### V 345 | 346 | **Vue:** 347 | ```vue 348 | 353 | 354 | 358 | ``` 359 | 360 | ### Y 361 | 362 | **YAML:** 363 | ```yaml 364 | greeting: Hello, World! 365 | author: Example 366 | version: 1.0 367 | features: 368 | - syntax-highlighting 369 | - code-blocks 370 | - astro-support 371 | ``` 372 | 373 | ### Z 374 | 375 | **Zig:** 376 | ```zig 377 | const std = @import("std"); 378 | pub fn main() !void { 379 | std.debug.print("Hello, World!\n", .{}); 380 | } 381 | ``` -------------------------------------------------------------------------------- /src/layouts/BaseLayout.astro: -------------------------------------------------------------------------------- 1 | --- 2 | export interface Props { 3 | title: string; 4 | description?: string; 5 | image?: string; 6 | } 7 | 8 | const { title, description = 'A terminal-inspired Astro theme', image } = Astro.props; 9 | const canonicalURL = new URL(Astro.url.pathname, Astro.site); 10 | const base = import.meta.env.BASE_URL.endsWith('/') ? import.meta.env.BASE_URL : import.meta.env.BASE_URL + '/'; 11 | --- 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {title} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {image && } 31 | 32 | 33 | 34 | 35 | {image && } 36 | 37 | 38 | 39 | 40 | 41 |
42 |
43 |
44 | 51 | 64 |
65 | 66 | 85 |
86 | 87 |
88 | 89 |
90 | 91 |
92 | 101 |
102 |
103 | 104 | 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Astro Terminal Theme 2 | 3 | I love both Astro and the Terminal theme by panr, so I decided to port this theme to Astro. This is an adaptation of the [Hugo Terminal Theme](https://github.com/panr/hugo-theme-terminal) created by [panr](https://github.com/panr). All design credit goes to the original author. 4 | 5 | ![Terminal Theme Screenshot](https://panr.github.io/hugo-theme-terminal-demo/img/terminal-css.png) 6 | 7 | - [Demo site](https://dennisklappe.github.io/astro-theme-terminal/) 8 | - [Terminal.css - Create your own colour scheme](https://panr.github.io/terminal-css/) 9 | 10 | ## Features 11 | 12 | - **Customisable colour schemes** — works with panr's [Terminal.css colour scheme generator](https://panr.github.io/terminal-css/) or choose from the default schemes available there 13 | - **[Fira Code](https://github.com/tonsky/FiraCode)** as default monospaced font — easily changeable 14 | - **nice syntax highlighting** — thanks to Astro's built-in Shiki support 15 | - **fully responsive** — works great on mobile and desktop 16 | - **tag support** — organise posts with tags and browse by tag 17 | - **RSS feed** — automatically generated RSS feed for your blog 18 | 19 | ## Requirements 20 | 21 | - Astro v5.0.0 or higher 22 | - Node.js 18 or higher 23 | 24 | ## Installation 25 | 26 | ### Clone repository 27 | 28 | ```bash 29 | git clone https://github.com/dennisklappe/astro-theme-terminal.git your-site-name 30 | cd your-site-name 31 | npm install 32 | ``` 33 | 34 | ### Use as a template 35 | 36 | You can also use this repository as a template on GitHub: 37 | 38 | 1. Click the "Use this template" button on the GitHub repository 39 | 2. Create a new repository from the template 40 | 3. Clone your new repository and install dependencies 41 | 42 | ## How to start 43 | 44 | ```bash 45 | npm run dev 46 | ``` 47 | 48 | ## How to build 49 | 50 | ```bash 51 | npm run build 52 | ``` 53 | 54 | ## Configuration 55 | 56 | ### Site Configuration 57 | 58 | Edit `astro.config.mjs`: 59 | 60 | ```js 61 | import { defineConfig } from 'astro/config'; 62 | 63 | export default defineConfig({ 64 | site: 'https://your-domain.com', 65 | markdown: { 66 | shikiConfig: { 67 | theme: 'css-variables', 68 | langs: [], 69 | wrap: true, 70 | }, 71 | }, 72 | }); 73 | ``` 74 | 75 | ### Theme Configuration 76 | 77 | The theme uses CSS custom properties for theming. To change colours, modify the variables in `src/styles/terminal.css`: 78 | 79 | ```css 80 | :root { 81 | --background: #1e2022; 82 | --foreground: #d6deeb; 83 | --accent: #ffa86a; 84 | --secondary: #8be9fd; 85 | --selection: #4c5f7a; 86 | --code-border: #4c5f7a; 87 | --comment: #637777; 88 | } 89 | ``` 90 | 91 | You can also use panr's [Terminal.css generator](https://panr.github.io/terminal-css/) to create your own colour scheme - this Astro port is fully compatible with the generated colour schemes. 92 | 93 | ### Navigation Menu 94 | 95 | Edit the navigation in `src/layouts/BaseLayout.astro`. The theme includes a dropdown menu for additional pages: 96 | 97 | ```astro 98 | 99 |
  • About
  • 100 |
  • Showcase
  • 101 | 102 | 103 | 108 | ``` 109 | 110 | ## Content 111 | 112 | ### Posts 113 | 114 | Create posts in `src/content/posts/`: 115 | 116 | ```md 117 | --- 118 | title: 'My First Post' 119 | description: 'This is my first blog post' 120 | pubDate: 2024-01-01 121 | author: 'Your Name' 122 | tags: ['astro', 'terminal'] 123 | --- 124 | 125 | Your content here... 126 | ``` 127 | 128 | ### Pages 129 | 130 | Create pages in `src/pages/`: 131 | 132 | ```astro 133 | --- 134 | import BaseLayout from '../layouts/BaseLayout.astro'; 135 | --- 136 | 137 | 138 |
    139 |

    About

    140 |

    Your content here...

    141 |
    142 |
    143 | ``` 144 | 145 | ## Syntax Highlighting 146 | 147 | The theme uses Astro's built-in Shiki syntax highlighter with a custom monochrome theme that matches the terminal aesthetic. Code blocks automatically get syntax highlighting: 148 | 149 | ```js 150 | // JavaScript example 151 | function hello() { 152 | console.log("Hello, World!"); 153 | } 154 | ``` 155 | 156 | ## Layouts 157 | 158 | ### BaseLayout 159 | 160 | The main layout that includes header, footer, and all necessary CSS imports. 161 | 162 | ### PostLayout 163 | 164 | Layout specifically for posts, includes metadata display and post navigation. 165 | 166 | ## Components 167 | 168 | - **Header** - Site header with terminal decoration 169 | - **Footer** - Site footer with copyright 170 | - **PostCard** - Post preview card 171 | - **Pagination** - Page navigation component 172 | - **FormattedDate** - Date formatting component 173 | 174 | ## Features 175 | 176 | ### Tags 177 | 178 | Posts can be organised with tags. Each tag gets its own page at `/tags/[tag-name]` showing all posts with that tag. A tag index page at `/tags` displays all available tags. 179 | 180 | 181 | ## Customization 182 | 183 | ### Fonts 184 | 185 | To change the monospace font, update the font import in `src/styles/fonts.css` and the font-family in `src/styles/terminal.css`. 186 | 187 | ### Colours 188 | 189 | Create your own colour scheme or choose from the default schemes using panr's [Terminal.css generator](https://panr.github.io/terminal-css/). 190 | 191 | ### CSS Structure 192 | 193 | The theme uses modular CSS files: 194 | - `terminal.css` - Core theme styles and variables 195 | - `fonts.css` - Font imports and utilities 196 | - `main.css` - Layout and utility classes 197 | - `header.css` - Header styles 198 | - `menu.css` - Navigation menu 199 | - `footer.css` - Footer styles 200 | - `post.css` - Post styles 201 | - `buttons.css` - Button components 202 | - `code.css` - Code block functionality 203 | - `syntax.css` - Syntax highlighting theme 204 | - `pagination.css` - Pagination styles 205 | - `gist.css` - GitHub Gist embed styles 206 | - `terms.css` - Terms and conditions styles 207 | 208 | ## Deployment 209 | 210 | ### GitHub Pages 211 | 212 | This theme includes a GitHub Actions workflow for automatic deployment to GitHub Pages: 213 | 214 | 1. Go to your repository Settings → Pages 215 | 2. Set Source to "GitHub Actions" 216 | 3. Push to the `main` branch or manually trigger the workflow 217 | 4. Your site will be available at `https://[username].github.io/astro-theme-terminal` 218 | 219 | To deploy to a custom domain or different base path, update the `site` and `base` options in `astro.config.mjs`. 220 | 221 | **Note**: The base path is only applied in production builds. During development, the site runs at the root path (`/`) for easier testing. 222 | 223 | ## Contributing 224 | 225 | If you find any bugs or have ideas for improvements, please open an issue or submit a pull request. 226 | 227 | ## Credits 228 | 229 | This theme is a port of the [Hugo Terminal Theme](https://github.com/panr/hugo-theme-terminal) created by [panr](https://github.com/panr). All design decisions, colour schemes, and visual aesthetics are credited to the original author. 230 | 231 | Astro port created by [Dennis Klappe](https://github.com/dennisklappe). 232 | 233 | ## License 234 | 235 | The original Hugo Terminal Theme is licensed under the MIT License. This Astro port maintains the same licence. 236 | 237 | Copyright for the original design: panr 238 | 239 | --- 240 | 241 | Made with love for the Astro community -------------------------------------------------------------------------------- /src/styles/terminal.css: -------------------------------------------------------------------------------- 1 | /* Fira Code: https://github.com/tonsky/FiraCode */ 2 | @import url('https://cdn.staticdelivr.com/gfonts/css2?family=Fira+Code:wght@300..700&display=swap'); 3 | 4 | :root { 5 | --background: #1a170f; 6 | --foreground: #eceae5; 7 | --accent: #eec35e; 8 | --radius: 0; 9 | --font-size: 1rem; 10 | --line-height: 1.54em; 11 | } 12 | 13 | html { 14 | box-sizing: border-box; 15 | } 16 | 17 | *, 18 | *:before, 19 | *:after { 20 | box-sizing: inherit; 21 | margin: 0; 22 | padding: 0; 23 | } 24 | 25 | body { 26 | font-family: 27 | "Fira Code", 28 | "JetBrains Mono", 29 | Monaco, 30 | Consolas, 31 | "Ubuntu Mono", 32 | monospace; 33 | font-size: var(--font-size); 34 | font-weight: 400; 35 | line-height: var(--line-height); 36 | background-color: var(--background); 37 | color: var(--foreground); 38 | text-rendering: optimizeLegibility; 39 | font-variant-ligatures: contextual; 40 | -webkit-overflow-scrolling: touch; 41 | -webkit-text-size-adjust: 100%; 42 | margin: 0; 43 | padding: 0; 44 | } 45 | 46 | h1 { 47 | font-size: calc(var(--font-size) * 1.45); 48 | letter-spacing: 0; 49 | } 50 | 51 | h2 { 52 | font-size: calc(var(--font-size) * 1.35); 53 | letter-spacing: 0; 54 | } 55 | 56 | h3 { 57 | font-size: calc(var(--font-size) * 1.15); 58 | letter-spacing: 0; 59 | } 60 | 61 | h4, 62 | h5, 63 | h6 { 64 | font-size: calc(var(--font-size) * 1); 65 | letter-spacing: 0; 66 | } 67 | 68 | h1, h2, h3, h4, h5, h6, 69 | p, ul, ol, 70 | img, figure, video, 71 | table { 72 | margin: 25px 0; 73 | } 74 | 75 | a { 76 | color: var(--accent); 77 | } 78 | 79 | button { 80 | position: relative; 81 | font: inherit; 82 | font-weight: bold; 83 | text-decoration: none; 84 | text-align: center; 85 | background: transparent; 86 | color: var(--accent); 87 | padding: 5px 18px; 88 | border: 4px solid var(--accent); 89 | border-radius: var(--radius); 90 | transition: background 0.15s linear; 91 | appearance: none; 92 | cursor: pointer; 93 | outline: none; 94 | } 95 | 96 | button:hover { 97 | background: color-mix(in srgb, var(--accent) 15%, transparent); 98 | } 99 | 100 | button:focus-visible, 101 | a:focus-visible { 102 | outline: 1px solid var(--accent); 103 | outline-offset: 2px; 104 | } 105 | 106 | fieldset { 107 | display: inline-block; 108 | border: 2px solid var(--foreground); 109 | border-radius: calc(var(--radius) * 1.6); 110 | padding: 10px; 111 | } 112 | 113 | fieldset *:first-child { 114 | margin-top: 0; 115 | } 116 | 117 | fieldset input, 118 | fieldset select, 119 | fieldset textarea, 120 | fieldset label, 121 | fieldset button { 122 | margin-top: calc(var(--line-height) * 0.5); 123 | width: 100%; 124 | } 125 | 126 | label { 127 | display: inline-block; 128 | } 129 | 130 | label input { 131 | margin-top: 0; 132 | } 133 | 134 | input, 135 | textarea, 136 | select { 137 | background: transparent; 138 | color: var(--foreground); 139 | border: 1px solid var(--foreground); 140 | border-radius: var(--radius); 141 | padding: 10px; 142 | font: inherit; 143 | appearance: none; 144 | } 145 | 146 | input[type="checkbox"] { 147 | width: auto; 148 | } 149 | 150 | input:focus-visible, 151 | input:active, 152 | textarea:focus-visible, 153 | textarea:active, 154 | select:focus-visible, 155 | select:active { 156 | border-color: var(--accent); 157 | outline: 1px solid var(--accent); 158 | outline-offset: 2px; 159 | } 160 | 161 | input:active, 162 | textarea:active, 163 | select:active { 164 | box-shadow: none; 165 | } 166 | 167 | select { 168 | background-image: linear-gradient( 169 | 45deg, 170 | transparent 50%, 171 | var(--foreground) 50% 172 | ), 173 | linear-gradient(135deg, var(--foreground) 50%, transparent 50%); 174 | background-position: calc(100% - 20px), calc(100% - 15px); 175 | background-size: 176 | 5px 5px, 177 | 5px 5px; 178 | background-repeat: no-repeat; 179 | padding-right: 40px; 180 | } 181 | 182 | select option { 183 | background: var(--background); 184 | } 185 | 186 | input[type="checkbox"], 187 | input[type="radio"] { 188 | vertical-align: middle; 189 | padding: 10px; 190 | box-shadow: inset 0 0 0 3px var(--background); 191 | } 192 | 193 | input[type="radio"] { 194 | display: inline-block; 195 | width: 10px !important; 196 | height: 10px !important; 197 | border-radius: 20px; 198 | } 199 | 200 | input[type="checkbox"]:checked, 201 | input[type="radio"]:checked { 202 | background: var(--accent); 203 | } 204 | 205 | img { 206 | display: block; 207 | max-width: 100%; 208 | border: 8px solid var(--accent); 209 | border-radius: var(--radius); 210 | padding: 8px; 211 | overflow: hidden; 212 | } 213 | 214 | figure img, 215 | figure video { 216 | margin-bottom: 0; 217 | } 218 | 219 | figure figcaption { 220 | background: var(--accent); 221 | color: var(--background); 222 | text-align: center; 223 | font-size: 1em; 224 | font-weight: normal; 225 | margin-top: -8px; 226 | border-radius: 0 0 var(--radius) var(--radius); 227 | } 228 | 229 | ul, 230 | ol { 231 | margin-left: 4ch; 232 | padding: 0; 233 | } 234 | 235 | ul ul, 236 | ul ol, 237 | ol ul, 238 | ol ol { 239 | margin-top: 0; 240 | } 241 | 242 | li::marker { 243 | color: var(--accent); 244 | } 245 | 246 | ul li, 247 | ol li { 248 | position: relative; 249 | } 250 | 251 | code, 252 | kbd { 253 | font-family: 254 | "Fira Code", 255 | "JetBrains Mono", 256 | Monaco, 257 | Consolas, 258 | Ubuntu Mono, 259 | monospace !important; 260 | font-feature-settings: normal; 261 | background: color-mix(in srgb, var(--foreground) 5%, transparent); 262 | color: color-mix(in srgb, var(--foreground) 5%, var(--accent)); 263 | padding: 0 6px; 264 | margin: 0 2px; 265 | font-size: 0.95em; 266 | } 267 | 268 | code { 269 | border: 1px solid color-mix(in srgb, var(--foreground) 25%, transparent); 270 | } 271 | 272 | kbd { 273 | border-top: 1px solid color-mix(in srgb, var(--accent) 25%, transparent); 274 | border-left: 1px solid var(--accent); 275 | border-right: 1px solid var(--accent); 276 | border-bottom: 4px solid var(--accent); 277 | border-radius: 4px; 278 | } 279 | 280 | code code { 281 | background: transparent; 282 | padding: 0; 283 | margin: 0; 284 | } 285 | 286 | pre { 287 | tab-size: 4; 288 | background: color-mix(in srgb, var(--foreground) 5%, transparent) !important; 289 | color: color-mix(in srgb, var(--foreground) 5%, var(--accent)); 290 | padding: 20px 10px; 291 | font-size: 0.95em !important; 292 | overflow: auto; 293 | border-radius: var(--radius); 294 | border: 1px solid color-mix(in srgb, var(--foreground) 25%, transparent); 295 | } 296 | 297 | pre code { 298 | background: none !important; 299 | margin: 0; 300 | padding: 0; 301 | font-size: inherit; 302 | border: none; 303 | } 304 | 305 | sup { 306 | line-height: 0; 307 | } 308 | 309 | abbr { 310 | position: relative; 311 | text-decoration-style: wavy; 312 | text-decoration-color: var(--accent); 313 | cursor: help; 314 | } 315 | 316 | sub { 317 | bottom: -0.25em; 318 | } 319 | 320 | sup { 321 | top: -0.25em; 322 | } 323 | 324 | mark { 325 | background: color-mix(in srgb, var(--accent) 45%, transparent); 326 | color: var(--foreground); 327 | } 328 | 329 | blockquote { 330 | position: relative; 331 | border-top: 1px solid var(--accent); 332 | border-bottom: 1px solid var(--accent); 333 | margin: 0; 334 | padding: 25px; 335 | } 336 | 337 | blockquote:before { 338 | content: ">"; 339 | display: block; 340 | position: absolute; 341 | left: 0; 342 | color: var(--accent); 343 | } 344 | 345 | blockquote p:first-child { 346 | margin-top: 0; 347 | } 348 | 349 | blockquote p:last-child { 350 | margin-bottom: 0; 351 | } 352 | 353 | table { 354 | table-layout: auto; 355 | border-collapse: collapse; 356 | } 357 | 358 | table, 359 | th, 360 | td { 361 | border: 2px solid var(--foreground); 362 | padding: 10px; 363 | } 364 | 365 | th { 366 | border-style: solid; 367 | color: var(--foreground); 368 | text-align: left; 369 | text-transform: uppercase; 370 | letter-spacing: 0.04em; 371 | } 372 | 373 | hr { 374 | width: 100%; 375 | border: none; 376 | background: var(--accent); 377 | height: 2px; 378 | } 379 | 380 | /* Bold elements */ 381 | 382 | h1, h2, h3, h4, h5, h6, 383 | b, strong, 384 | th, 385 | button { 386 | font-weight: 600; 387 | } 388 | -------------------------------------------------------------------------------- /src/content/posts/lorem-ipsum.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Lorem Ipsum' 3 | description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.' 4 | pubDate: 2019-03-10 5 | author: 'Lorem Ipsum' 6 | tags: [] 7 | --- 8 | 9 | ## Quisque lectus turpis 10 | 11 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed luctus arcu vel lacus tincidunt, sit amet vestibulum augue vestibulum. Mauris eget massa id ante consequat efficitur. Mauris sit amet augue ut nisi placerat malesuada. Etiam feugiat eros sit amet nunc ultrices iaculis. Curabitur in lorem at justo tempor accumsan quis vel sapien. Phasellus posuere rutrum libero, laoreet scelerisque turpis. Morbi tortor justo, imperdiet et blandit vel, volutpat lobortis ante. Nulla ullamcorper leo felis. 12 | 13 | Sed consequat nisl nec magna iaculis tristique. Phasellus in dapibus quam. Vestibulum in auctor sapien, non sodales neque. Quisque lectus turpis, blandit eget porta vitae, lobortis eu sem. Aenean elementum odio at urna porttitor, ac dapibus risus elementum. Nam eu libero non augue tincidunt aliquet eu ac augue. Duis in velit sit amet quam iaculis dictum sit amet molestie libero. Vivamus ac justo quis velit fringilla finibus. Nunc quis sapien est. Nam elit ligula, elementum nec dui ut, imperdiet tempus enim. Ut malesuada iaculis viverra. Phasellus ultrices malesuada leo non faucibus. Sed volutpat mollis fermentum. Etiam rutrum et justo in condimentum. 14 | 15 | Sed condimentum nibh in eros varius, a commodo urna volutpat. Curabitur fringilla ultricies orci. Suspendisse commodo mi eu arcu sagittis hendrerit. Nunc elementum lacus id turpis lobortis tempor. Aliquam sed dapibus lorem. Fusce ultrices sapien eget lacus iaculis pretium. Vestibulum posuere erat nec ante cursus molestie ac ut leo. Pellentesque quis tortor tortor. Nullam sem felis, vehicula in mi nec, posuere tincidunt justo. Interdum et malesuada fames ac ante ipsum primis in faucibus. Morbi faucibus dolor at aliquet eleifend. Maecenas ac magna in orci sodales molestie fermentum eget odio. Sed tincidunt pretium felis eu venenatis. Donec a augue vitae arcu maximus tempus posuere eu ligula. Etiam pretium, purus ac vehicula dictum, arcu elit ultrices sapien, vel feugiat sapien velit in ligula. 16 | 17 | ## Phasellus ultrices 18 | 19 | Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Quisque maximus, nibh viverra facilisis feugiat, felis tortor facilisis nisi, ac fringilla eros nisl et augue. Sed convallis lacus nulla, quis rutrum eros condimentum ac. Etiam pretium libero eget metus finibus, id venenatis leo aliquam. Duis nunc mi, iaculis sit amet ultricies a, tempor auctor nibh. Quisque semper, massa ac varius congue, diam enim commodo justo, in tincidunt sem justo at tellus. Nam porta sagittis sodales. Quisque eget vestibulum velit. Suspendisse id bibendum ex, sed rutrum arcu. 20 | 21 | ### Cras mollis 22 | 23 | Phasellus quis vulputate dui. Proin et sem finibus, placerat urna sed, mollis ex. In hac habitasse platea dictumst. Duis ac diam semper lacus dignissim ornare vitae vel nulla. Etiam maximus porttitor odio, eget convallis sem. Cras mollis dui erat, et molestie odio fringilla in. Praesent tincidunt ex nec nisl mattis malesuada. Sed scelerisque felis ac metus feugiat, sed euismod ligula vestibulum. 24 | 25 | ### Praesent pulvinar 26 | 27 | Maecenas sed vestibulum turpis. Fusce eleifend, libero ut pellentesque condimentum, ante sapien pellentesque risus, vulputate placerat dolor nibh eget sapien. In et rutrum felis, in mollis velit. Curabitur vehicula orci eget purus hendrerit condimentum. Nullam pharetra augue nec sagittis vehicula. Donec id tristique mauris. Mauris a egestas neque. Praesent pulvinar commodo tellus eu viverra. 28 | 29 | ### Vestibulum congue 30 | 31 | #### Phasellus vitae 32 | 33 | Curabitur consectetur sapien ac nibh aliquam sollicitudin. Proin lobortis suscipit enim, id hendrerit ipsum eleifend quis. Donec vel tortor ut lorem porttitor maximus. Praesent quis laoreet est, sed suscipit nisl. Vestibulum congue finibus tempus. Ut et venenatis lorem. Donec tincidunt ultrices congue. Morbi eu nibh nisl. Vivamus suscipit risus scelerisque risus aliquam faucibus. Phasellus vitae vulputate elit, non commodo nunc. Nunc laoreet, nunc et iaculis sagittis, ligula nibh elementum quam, eu convallis massa nunc vel odio. 34 | 35 | Duis ullamcorper ex eu lorem accumsan, non congue tellus mollis. Curabitur nec ipsum vel dolor vestibulum consectetur. Maecenas magna orci, finibus vel nunc non, maximus lacinia nisl. Donec euismod in augue at efficitur. In mollis scelerisque sem id lobortis. Cras suscipit eu purus eget consequat. Proin posuere tortor purus, id pharetra quam consequat in. Sed vel nisl lacus. Ut fringilla elementum malesuada. Sed maximus faucibus ligula quis suscipit. Nulla viverra imperdiet enim sit amet vulputate. Nulla pulvinar in nunc quis cursus. Pellentesque eros tortor, tincidunt eu quam a, laoreet varius sapien. 36 | 37 | #### Integer porttitor 38 | 39 | Aliquam vestibulum dictum ipsum, ac mollis odio vehicula vitae. Nulla at lorem condimentum, sollicitudin ex non, lacinia quam. Quisque ante tortor, luctus et enim eget, egestas eleifend enim. Ut tempor magna lacus, sed bibendum nunc ultricies ut. Nunc tincidunt elementum venenatis. Integer porttitor eget purus aliquet auctor. Aliquam vitae augue rutrum, bibendum justo vehicula, volutpat arcu. Donec ut vulputate libero, at egestas dolor. Aenean ut venenatis erat, eget ultricies arcu. Nam eget magna convallis, rutrum mi eu, molestie urna. In at diam ultricies, fringilla ante feugiat, vulputate diam. Nunc efficitur dictum nibh vel rutrum. Suspendisse efficitur, nibh et congue finibus, enim libero fringilla magna, non aliquam enim ligula sed diam. Morbi lacinia lobortis vestibulum. Integer gravida turpis convallis lorem vehicula, condimentum volutpat tellus tempus. 40 | 41 | #### Pellentesque 42 | 43 | Donec imperdiet massa sed nulla consectetur eleifend. Ut turpis dui, iaculis eu eleifend vel, molestie varius sem. Proin interdum vitae lacus nec condimentum. Pellentesque ultricies sit amet lacus et imperdiet. Donec suscipit sagittis orci scelerisque efficitur. Suspendisse potenti. Praesent imperdiet erat sed turpis tincidunt aliquam. Suspendisse nec dignissim quam. Sed imperdiet massa quis ullamcorper sagittis. Integer et ipsum sed velit tincidunt facilisis. Aenean ut accumsan tellus. Fusce nulla odio, iaculis quis lobortis ac, euismod vel ex. Aenean quis nisi vehicula, faucibus massa et, pulvinar dui. Aliquam sed elit eu massa laoreet blandit. 44 | 45 | Nulla tempor nec enim nec rutrum. In eget metus molestie, eleifend turpis at, rutrum mauris. Ut sed pharetra mauris. Proin lacus urna, sollicitudin sed felis vel, elementum auctor ligula. Suspendisse vel pellentesque quam. Cras ac justo nec nunc pulvinar dapibus. Fusce dapibus congue felis, ac lobortis magna tempor et. Mauris porta sed erat vel eleifend. 46 | 47 | Vestibulum a cursus dolor. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Cras ac ipsum finibus, sodales dui sed, finibus ligula. Sed placerat magna in semper faucibus. Aliquam condimentum, turpis ac cursus gravida, elit augue cursus mauris, quis lobortis sem quam ut mauris. Cras a ligula risus. Donec sit amet augue dui. Vivamus rutrum eleifend mi sit amet sollicitudin. Phasellus vel ligula ut magna scelerisque auctor quis ut odio. Nam sed tellus viverra, sodales nibh vitae, consequat nunc. In finibus fermentum lacus a aliquam. Aliquam tellus tortor, interdum vel justo non, blandit lacinia massa. Sed finibus pulvinar eros, sit amet sollicitudin magna faucibus at. Aenean elementum vulputate tortor. 48 | 49 | Praesent dignissim massa diam, ac accumsan lorem pulvinar et. Suspendisse placerat urna quis mauris interdum porta. Morbi ut massa sed turpis porta consequat. Etiam sagittis sed lectus non rutrum. Vestibulum arcu justo, congue a massa sed, posuere mollis justo. Morbi viverra purus ac ex eleifend, ut sollicitudin arcu porta. In hac habitasse platea dictumst. In sed risus sed dui malesuada bibendum. Vestibulum suscipit, ipsum eu eleifend eleifend, nunc nulla tincidunt metus, non dignissim ligula dui ut nulla. Sed dignissim ornare lectus. Nam consectetur id dui a tincidunt. Aenean accumsan lectus quis molestie rutrum. Sed mattis dolor mauris, sed rhoncus neque accumsan nec. 50 | 51 | Ut urna sapien, vehicula sed consequat vel, faucibus sed tellus. Cras tristique interdum posuere. Curabitur nunc sem, pellentesque a laoreet at, accumsan sed justo. Proin sodales augue sed dui gravida feugiat. Nam at ipsum neque. Aliquam eleifend consequat sagittis. Proin lectus nunc, rutrum in magna nec, accumsan fermentum felis. Nunc scelerisque facilisis lorem, at tincidunt mi bibendum tincidunt. Ut ut nibh vitae sem gravida rutrum. Donec malesuada, eros vitae molestie malesuada, nisl mauris maximus eros, non rhoncus lectus odio sit amet velit. Proin a tellus vel mauris rhoncus rutrum eget vitae mauris. Fusce in mi justo. Sed et mauris nisl. Sed eu nisl rhoncus, tempus massa eu, maximus urna. Etiam tincidunt vestibulum nibh sed condimentum. 52 | 53 | Nulla a tortor dui. Donec vestibulum justo mauris, vel commodo nunc volutpat vel. Mauris eleifend libero non metus dictum malesuada. Aliquam sodales tortor neque, vel rutrum arcu commodo nec. Maecenas vulputate feugiat tempor. Aenean mauris massa, laoreet et ex non, tincidunt placerat nunc. Nullam vulputate neque id rutrum pharetra. Duis ultricies mauris nisl, tempor tempor ante rhoncus quis. Maecenas elementum velit ut arcu euismod tincidunt. Sed eu urna condimentum, eleifend purus sit amet, elementum tellus. Phasellus fermentum arcu tellus, nec mollis orci vestibulum vitae. 54 | 55 | Etiam eu eleifend felis. Sed non nibh vel odio varius aliquet et eget mi. Donec eget risus id mi dapibus bibendum. Suspendisse nisi dui, varius non rhoncus vitae, hendrerit et nulla. Sed a tellus sodales, gravida enim nec, commodo orci. Nullam at gravida risus. In hac habitasse platea dictumst. Aliquam erat volutpat. In a nunc vulputate, accumsan mauris eu, ullamcorper felis. Donec dignissim purus metus. Vestibulum nec vestibulum ante, et placerat enim. 56 | 57 | Proin ullamcorper ante odio, at vulputate mauris lobortis eget. Nulla hendrerit mauris non dictum auctor. Mauris lacinia, velit in scelerisque volutpat, dolor nunc semper justo, sodales sagittis tortor quam nec nisi. Nam varius elit nec maximus rhoncus. Duis non eros mauris. Integer tempor venenatis sapien, ut ullamcorper nisl pulvinar quis. Cras elementum lacus vel lorem elementum iaculis. --------------------------------------------------------------------------------