commit
e1f348e2bd
3
.gitignore
vendored
3
.gitignore
vendored
@ -2,4 +2,5 @@ node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
src/config.json
|
||||
src/config.json
|
||||
public
|
@ -1,15 +1,21 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "Replacing HTMLTITLE with ${title} in /app/index.html"
|
||||
sed -i -e 's/HTMLTITLE/'"${TITLE}"'/g' /app/index.html
|
||||
|
||||
echo "Replacing HTMLTITLE with ${title} in /app/src/main.tsx"
|
||||
sed -i -e 's/HTMLTITLE/'"${TITLE}"'/g' /app/src/main.tsx
|
||||
|
||||
# Escape slashes... apparently
|
||||
# Escape slashes
|
||||
LOGO=${LOGO//\//\\/}
|
||||
|
||||
echo "Replacing LOGO with ${LOGO} in /app/src/main.tsx"
|
||||
sed -i -e 's/LOGO/'"${LOGO}"'/g' /app/src/main.tsx
|
||||
# HTML replacement
|
||||
sed -i -e 's/HTMLTITLE/'"${TITLE}"'/g' /app/index.html
|
||||
|
||||
# TypeScript replacement
|
||||
sed -i -e 's/PAGETITLE = "My Website"/PAGETITLE = "'"${TITLE}"'"/g' /app/src/variables.ts
|
||||
sed -i -e 's/PAGEICON = "\/logo\.png"/PAGEICON = "'"${LOGO}"'"/g' /app/src/variables.ts
|
||||
sed -i -e 's/SHOWHEADER = true/SHOWHEADER = '"${HEADER}"'/g' /app/src/variables.ts
|
||||
sed -i -e 's/SHOWHEADERLINE = true/SHOWHEADERLINE = '"${HEADERLINE}"'/g' /app/src/variables.ts
|
||||
sed -i -e 's/SHOWHEADERTOP = false/SHOWHEADERTOP = '"${HEADERTOP}"'/g' /app/src/variables.ts
|
||||
sed -i -e 's/CATEGORIES = "normal"/CATEGORIES = "'"${CATEGORIES}"'"/g' /app/src/variables.ts
|
||||
|
||||
# CSS replacement
|
||||
sed -i -e 's/background-color: theme(\(colors\.slate\.50\))/background-color: '"${BGCOLOR}"'/g' /app/src/tailwind.css
|
||||
sed -i -e 's/background-color: theme(\(colors\.gray\.950\))/background-color: '"${BGCOLORDARK}"'/g' /app/src/tailwind.css
|
||||
|
||||
exec "$@"
|
@ -11,6 +11,12 @@ ENV NODE_ENV production
|
||||
|
||||
ENV TITLE "My Website"
|
||||
ENV LOGO "/logo.png"
|
||||
ENV HEADER "true"
|
||||
ENV HEADERLINE "true"
|
||||
ENV HEADERTOP "false"
|
||||
ENV CATEGORIES "normal"
|
||||
ENV BGCOLOR "theme(colors.slate.50)"
|
||||
ENV BGCOLORDARK "theme(colors.gray.950)"
|
||||
|
||||
EXPOSE 4173
|
||||
|
||||
|
8
package-lock.json
generated
8
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "sb80",
|
||||
"version": "1.0.0",
|
||||
"name": "starbase-80",
|
||||
"version": "1.0.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "sb80",
|
||||
"version": "1.0.0",
|
||||
"name": "starbase-80",
|
||||
"version": "1.0.1",
|
||||
"dependencies": {
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
|
120
readme.md
120
readme.md
@ -6,15 +6,71 @@
|
||||
|
||||
# About
|
||||
|
||||
<img src="./preview.jpg" alt="" />
|
||||
|
||||
A nice looking homepage for Docker containers or any services and links.
|
||||
|
||||
No actual integration with Docker. Loads instantly. No dark theme.
|
||||
No actual integration with Docker. Loads instantly. Dark mode follows your OS.
|
||||
|
||||
If you make a change to the config JSON, restart this container and refresh.
|
||||
|
||||
Provide your own icons.
|
||||
Inspired by [Ben Phelps' Homepage](https://gethomepage.dev/) and [Umbrel](https://umbrel.com/).
|
||||
|
||||
<img src="./preview.jpg" alt="" />
|
||||
|
||||
# Icons
|
||||
|
||||
## Use your own
|
||||
|
||||
Create a volume or bind mount to a subfolder of `/app/public` and specify a relative path.
|
||||
|
||||
```bash
|
||||
# Your folder
|
||||
compose.yml
|
||||
- icons
|
||||
- jellyfin.jpg
|
||||
- ghost.jpg
|
||||
- etc
|
||||
|
||||
# Bind mount
|
||||
./icons:/app/public/icons
|
||||
|
||||
# Specify an icon in config.json
|
||||
"icon": "/icons/jellyfin.jpg"
|
||||
```
|
||||
|
||||
## Dashboard icons
|
||||
|
||||
Use [Dashboard icons](https://github.com/walkxcode/dashboard-icons) by specifying a name without any prefix.
|
||||
|
||||
```bash
|
||||
# Specify an icon in config.json
|
||||
"icon": "jellyfin"
|
||||
```
|
||||
|
||||
## Material design
|
||||
|
||||
Use any [Material Design icon](https://icon-sets.iconify.design/mdi/) by prefixing the name with `mdi-`.
|
||||
|
||||
Fill the icon by providing an "iconColor" from the [list of Tailwind colors](https://tailwindcss.com/docs/background-color). Do not prefix with "bg-".
|
||||
|
||||
Use "black" or "white" for those colors.
|
||||
|
||||
```bash
|
||||
# Specify an icon in config.json
|
||||
"icon": "mdi-cloud",
|
||||
"iconColor": "black"
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
```bash
|
||||
# Specify an icon in config.json
|
||||
"icon": "/icons/jellyfin.jpg", # mostly required, but if set to "" it removes the icon
|
||||
"iconColor": "blue-500", # optional, defaults to a contrasting color
|
||||
"iconBG": "gray-200", # optional, defaults to a complementary color
|
||||
"iconBubble": false, # optional, defaults to true, turns off bubble and shadow when false
|
||||
```
|
||||
|
||||
For `iconColor` and `iconBG`, use a [Tailwind color](https://tailwindcss.com/docs/background-color). Turn off background color with a value of `"transparent"`. Do not prefix with `"bg-"`.
|
||||
|
||||
# Docker compose
|
||||
|
||||
@ -25,8 +81,14 @@ services:
|
||||
ports:
|
||||
- 4173:4173
|
||||
environment:
|
||||
- TITLE=My Homepage
|
||||
- LOGO=/logo.png
|
||||
- TITLE=Starbase 80 # defaults to "My Website", set to TITLE= to hide the title
|
||||
- LOGO=/starbase80.jpg # defaults to /logo.png, set to LOGO= to hide the logo
|
||||
- HEADER=true # defaults to true, set to false to hide the title and logo
|
||||
- HEADERLINE=true # defaults to true, set to false to turn off the header border line
|
||||
- HEADERTOP=true # defaults to false, set to true to force the header to always stay on top
|
||||
- CATEGORIES=small # defaults to normal, set to small for smaller, uppercase category labels
|
||||
- BGCOLOR=#fff # defaults to theme(colors.slate.50), set to any hex color or Tailwind color using the theme syntax
|
||||
- BGCOLORDARK=#000 # defaults to theme(colors.gray.950), set to any hex color or Tailwind color using the theme syntax
|
||||
volumes:
|
||||
- ./config.json:/app/src/config.json # required
|
||||
- ./public/favicon.ico:/app/public/favicon.ico # optional
|
||||
@ -36,6 +98,48 @@ services:
|
||||
|
||||
# config.json format
|
||||
|
||||
## Categories
|
||||
|
||||
Can have as many categories as you like.
|
||||
|
||||
- **category**: Title, optional, displays above services
|
||||
- **bubble**: boolean, optional, defaults to false, shows a bubble around category
|
||||
- **services**: Array of services
|
||||
|
||||
## Service
|
||||
|
||||
- **name**: Name, required
|
||||
- **uri**: Hyperlink, required
|
||||
- **description**: 2-3 words, optional
|
||||
- **icon**: optional, relative URI, absolute URI, service name ([Dashboard icon](https://github.com/walkxcode/dashboard-icons)) or `mdi-`service name ([Material Design icon](https://icon-sets.iconify.design/mdi/))
|
||||
- **iconBG**: optional, hex code or [Tailwind color](https://tailwindcss.com/docs/background-color) (do not prefix with `bg-`). Background color for icons.
|
||||
- **iconColor**: optional, hex code or [Tailwind color](https://tailwindcss.com/docs/background-color) (do not prefix with `bg-`). Only used as the fill color for Material Design icons.
|
||||
- **iconBubble**: option, `true` or `false`, when `false` the bubble and shadow are removed from the icon
|
||||
|
||||
## Template
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"category": "Category name",
|
||||
"bubble": false,
|
||||
"services": [
|
||||
{
|
||||
"name": "My Cloud App",
|
||||
"uri": "https://website.com",
|
||||
"description": "Fun site",
|
||||
"icon": "mdi-cloud",
|
||||
"iconBG": "#fff",
|
||||
"iconColor": "#000",
|
||||
"iconBubble": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
@ -63,6 +167,7 @@ services:
|
||||
},
|
||||
{
|
||||
"category": "Devices",
|
||||
"bubble": true,
|
||||
"services": [
|
||||
{
|
||||
"name": "Router",
|
||||
@ -74,7 +179,8 @@ services:
|
||||
"name": "Home Assistant",
|
||||
"uri": "http://homeassistant.local:8123/",
|
||||
"description": "Home automation",
|
||||
"icon": "/icons/home-assistant.svg"
|
||||
"icon": "home-assistant",
|
||||
"iconBubble": false
|
||||
},
|
||||
{
|
||||
"name": "Synology",
|
||||
|
@ -23,16 +23,20 @@ const iconLevel = 300;
|
||||
const getIconColor = (index: number) => `bg-${iconColors[iconColors.length % index]}-${iconLevel}`;
|
||||
|
||||
interface IProps {
|
||||
name: string;
|
||||
index: number;
|
||||
icon?: string;
|
||||
iconColor?: string;
|
||||
iconBG?: string;
|
||||
iconBubble?: boolean;
|
||||
uri?: string;
|
||||
}
|
||||
|
||||
export const Icon: React.FunctionComponent<IProps> = ({ uri, icon, index }) => {
|
||||
export const Icon: React.FunctionComponent<IProps> = ({ name, uri, icon, index, iconBG, iconBubble, iconColor }) => {
|
||||
if (is.null(icon)) {
|
||||
if (!is.null(uri)) {
|
||||
return (
|
||||
<a href={uri} target="_blank">
|
||||
<a href={uri} target="_blank" rel="noreferrer" title={name}>
|
||||
<IconBlank index={index} />
|
||||
</a>
|
||||
);
|
||||
@ -43,13 +47,13 @@ export const Icon: React.FunctionComponent<IProps> = ({ uri, icon, index }) => {
|
||||
|
||||
if (!is.null(uri)) {
|
||||
return (
|
||||
<a href={uri} target="_blank">
|
||||
<IconBase icon={icon as string} />
|
||||
<a href={uri} target="_blank" rel="noreferrer" title={name}>
|
||||
<IconBase icon={icon as string} iconBG={iconBG} iconColor={iconColor} iconBubble={iconBubble} />
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return <IconBase icon={icon as string} />;
|
||||
return <IconBase icon={icon as string} iconBG={iconBG} iconColor={iconColor} iconBubble={iconBubble} />;
|
||||
};
|
||||
|
||||
interface IIconBlankProps {
|
||||
@ -66,16 +70,108 @@ const IconBlank: React.FunctionComponent<IIconBlankProps> = ({ index }) => {
|
||||
);
|
||||
};
|
||||
|
||||
interface IIconBaseProps {
|
||||
icon: string;
|
||||
enum IconType {
|
||||
uri,
|
||||
material,
|
||||
dashboard,
|
||||
}
|
||||
|
||||
const IconBase: React.FunctionComponent<IIconBaseProps> = ({ icon }) => {
|
||||
return (
|
||||
<img
|
||||
src={icon}
|
||||
alt=""
|
||||
className=" block w-16 h-16 rounded-2xl border border-black/5 shadow-sm overflow-hidden"
|
||||
/>
|
||||
);
|
||||
interface IIconBaseProps {
|
||||
icon: string;
|
||||
iconColor?: string;
|
||||
iconBG?: string;
|
||||
iconBubble?: boolean;
|
||||
}
|
||||
|
||||
const IconBase: React.FunctionComponent<IIconBaseProps> = ({ icon, iconBG, iconBubble, iconColor }) => {
|
||||
let iconType: IconType = IconType.uri;
|
||||
|
||||
if (icon.startsWith("http") || icon.startsWith("/")) {
|
||||
iconType = IconType.uri;
|
||||
} else if (icon.startsWith("mdi-")) {
|
||||
iconType = IconType.material;
|
||||
} else {
|
||||
iconType = IconType.dashboard;
|
||||
}
|
||||
|
||||
// Everyone starts the same size
|
||||
let iconClassName = "block w-16 h-16 overflow-hidden bg-contain";
|
||||
|
||||
if (is.null(iconBubble) || iconBubble !== false) {
|
||||
iconClassName += " rounded-2xl border border-black/5 shadow-sm";
|
||||
}
|
||||
|
||||
const iconStyle: React.CSSProperties = {};
|
||||
|
||||
switch (iconType) {
|
||||
case IconType.uri:
|
||||
case IconType.dashboard:
|
||||
// Default to bubble and no background for URI and Dashboard icons
|
||||
if (!is.null(iconBG)) {
|
||||
if (iconBG?.startsWith("#")) {
|
||||
iconStyle.backgroundColor = iconBG;
|
||||
} else {
|
||||
iconClassName += ` bg-${iconBG}`;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case IconType.material:
|
||||
// Material icons get a color and a background by default, then an optional bubble
|
||||
if (is.null(iconBG)) {
|
||||
iconClassName += ` bg-slate-200 dark:bg-gray-900`;
|
||||
} else {
|
||||
if (iconBG?.startsWith("#")) {
|
||||
iconStyle.backgroundColor = iconBG;
|
||||
} else {
|
||||
iconClassName += ` bg-${iconBG}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (is.null(iconBubble) || iconBubble !== false) {
|
||||
iconClassName += " rounded-2xl border border-black/5 shadow-sm";
|
||||
}
|
||||
|
||||
if (is.null(iconColor)) {
|
||||
iconColor = "black dark:bg-white";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const mdiIconStyle: React.CSSProperties = {};
|
||||
let mdiIconColorFull = "bg-" + iconColor;
|
||||
|
||||
if (!is.null(iconColor) && iconColor?.startsWith("#")) {
|
||||
mdiIconColorFull = "";
|
||||
mdiIconStyle.backgroundColor = iconColor;
|
||||
}
|
||||
|
||||
switch (iconType) {
|
||||
case IconType.uri:
|
||||
return <img src={icon} alt="" className={iconClassName} style={{ ...iconStyle }} />;
|
||||
case IconType.dashboard:
|
||||
icon = icon.replace(".png", "").replace(".jpg", "").replace(".svg", "");
|
||||
return (
|
||||
<img
|
||||
src={`https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${icon}.png`}
|
||||
alt=""
|
||||
className={iconClassName}
|
||||
style={{ ...iconStyle }}
|
||||
/>
|
||||
);
|
||||
case IconType.material:
|
||||
icon = icon.replace("mdi-", "").replace(".svg", "");
|
||||
|
||||
return (
|
||||
<div className={iconClassName} style={{ ...iconStyle }}>
|
||||
<div
|
||||
className={`block w-16 h-16 ${mdiIconColorFull} overflow-hidden`}
|
||||
style={{
|
||||
...mdiIconStyle,
|
||||
mask: `url(https://cdn.jsdelivr.net/npm/@mdi/svg@latest/svg/${icon}.svg) no-repeat center / contain`,
|
||||
WebkitMask: `url(https://cdn.jsdelivr.net/npm/@mdi/svg@latest/svg/${icon}.svg) no-repeat center / contain`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -1,20 +1,50 @@
|
||||
import React from "react";
|
||||
import { IServiceCatalog } from "../shared/types";
|
||||
import { CATEGORIES } from "../variables";
|
||||
import { Services } from "./services";
|
||||
|
||||
interface IProps {
|
||||
catalogs: IServiceCatalog[];
|
||||
}
|
||||
|
||||
export const ServiceCatalogs: React.FunctionComponent<IProps> = ({ catalogs }) => {
|
||||
export const ServiceCatalogList: React.FunctionComponent<IProps> = ({ catalogs }) => {
|
||||
return (
|
||||
<ul>
|
||||
{catalogs.map((catalog, index) => (
|
||||
<li key={index} className="mt-12 first:mt-0 xl:first:mt-6">
|
||||
<h2 className="text-2xl text-slate-600 font-light py-2 px-4">{catalog.category}</h2>
|
||||
<Services services={catalog.services} />
|
||||
</li>
|
||||
<ServiceCatalog key={index} catalog={catalog} index={index} />
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
interface ICatalogProps {
|
||||
catalog: IServiceCatalog;
|
||||
index: number;
|
||||
}
|
||||
|
||||
const ServiceCatalog: React.FunctionComponent<ICatalogProps> = ({ catalog, index }) => {
|
||||
let categoryClassName = "dark:text-slate-200";
|
||||
|
||||
switch (CATEGORIES as string) {
|
||||
case "small":
|
||||
categoryClassName += " text-sm text-slate-800 font-semibold py px-4 uppercase";
|
||||
break;
|
||||
case "normal":
|
||||
default:
|
||||
categoryClassName += " text-2xl text-slate-600 font-light py-2 px-4";
|
||||
break;
|
||||
}
|
||||
|
||||
let liClassName = "mt-12 first:mt-0 xl:first:mt-6";
|
||||
|
||||
if (catalog.bubble) {
|
||||
liClassName += " bg-white dark:bg-black rounded-2xl px-6 py-6 ring-1 ring-slate-900/5 shadow-xl";
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={index} className={liClassName}>
|
||||
<h2 className={categoryClassName}>{catalog.category}</h2>
|
||||
<Services services={catalog.services} />
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
@ -23,13 +23,23 @@ interface IServiceProps {
|
||||
}
|
||||
|
||||
const Service: React.FunctionComponent<IServiceProps> = ({ service, index }) => {
|
||||
const { name, uri, description, icon } = service;
|
||||
const { name, uri, description, icon, iconBG, iconBubble, iconColor } = service;
|
||||
|
||||
return (
|
||||
<li className="p-4 flex gap-4">
|
||||
<span className="flex-shrink-0 block ">
|
||||
<Icon icon={icon} uri={uri} index={index} />
|
||||
</span>
|
||||
{!is.null(icon) && (
|
||||
<span className="flex-shrink-0 block">
|
||||
<Icon
|
||||
name={name}
|
||||
icon={icon}
|
||||
uri={uri}
|
||||
index={index}
|
||||
iconColor={iconColor}
|
||||
iconBG={iconBG}
|
||||
iconBubble={iconBubble}
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
<div>
|
||||
<h3 className="text-lg mt-1 font-semibold line-clamp-1">
|
||||
<a href={uri} target="_blank">
|
||||
@ -37,7 +47,7 @@ const Service: React.FunctionComponent<IServiceProps> = ({ service, index }) =>
|
||||
</a>
|
||||
</h3>
|
||||
{!is.null(description) && (
|
||||
<p className="text-sm text-black/50 line-clamp-1">
|
||||
<p className="text-sm text-black/50 dark:text-white/50 line-clamp-1">
|
||||
<a href={uri} target="_blank">
|
||||
{description}
|
||||
</a>
|
||||
|
@ -2,9 +2,10 @@ import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { IndexPage } from "./pages";
|
||||
import "./tailwind.css";
|
||||
import { PAGEICON, PAGETITLE } from "./variables";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||
<React.StrictMode>
|
||||
<IndexPage title="HTMLTITLE" icon="LOGO" />
|
||||
<IndexPage title={PAGETITLE} icon={PAGEICON} />
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
@ -1,8 +1,9 @@
|
||||
import React from "react";
|
||||
import { Header } from "../components/header";
|
||||
import { ServiceCatalogs } from "../components/service-catalogs";
|
||||
import { ServiceCatalogList } from "../components/service-catalogs";
|
||||
import userServices from "../config.json";
|
||||
import { IServiceCatalog } from "../shared/types";
|
||||
import { SHOWHEADER, SHOWHEADERLINE, SHOWHEADERTOP } from "../variables";
|
||||
|
||||
interface IProps {
|
||||
title?: string;
|
||||
@ -12,13 +13,47 @@ interface IProps {
|
||||
export const IndexPage: React.FunctionComponent<IProps> = ({ icon, title }) => {
|
||||
const mySerices = userServices as IServiceCatalog[];
|
||||
|
||||
let headerClassName = "p-4";
|
||||
|
||||
if (SHOWHEADERTOP) {
|
||||
headerClassName += " w-full";
|
||||
} else {
|
||||
headerClassName += " w-full xl:w-auto xl:max-w-xs xl:min-h-screen";
|
||||
}
|
||||
|
||||
if (SHOWHEADERLINE) {
|
||||
headerClassName += "border-0 border-solid border-gray-300 dark:border-gray-700";
|
||||
|
||||
if (SHOWHEADERTOP) {
|
||||
headerClassName += " border-b";
|
||||
} else {
|
||||
headerClassName += " border-b xl:border-r xl:border-b-0";
|
||||
}
|
||||
}
|
||||
|
||||
let pageWrapperClassName = "min-h-screen flex flex-col max-w-screen-2xl mx-auto";
|
||||
|
||||
if (!SHOWHEADERTOP) {
|
||||
pageWrapperClassName += " xl:flex-row";
|
||||
}
|
||||
|
||||
let serviceCatalogListWrapperClassName = "p-4 flex-grow";
|
||||
|
||||
if (!SHOWHEADERTOP) {
|
||||
serviceCatalogListWrapperClassName += " min-h-screen";
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col xl:flex-row">
|
||||
<div className="w-full xl:w-auto xl:max-w-xs xl:min-h-screen border-0 border-solid border-b xl:border-r xl:border-b-0 border-gray-300 p-4">
|
||||
<Header title={title} icon={icon} />
|
||||
</div>
|
||||
<div className="min-h-screen p-4 flex-grow">
|
||||
<ServiceCatalogs catalogs={mySerices} />
|
||||
<div className="min-h-screen">
|
||||
<div className={pageWrapperClassName}>
|
||||
{SHOWHEADER && (
|
||||
<div className={headerClassName}>
|
||||
<Header title={title} icon={icon} />
|
||||
</div>
|
||||
)}
|
||||
<div className={serviceCatalogListWrapperClassName}>
|
||||
<ServiceCatalogList catalogs={mySerices} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,5 +1,6 @@
|
||||
export interface IServiceCatalog {
|
||||
category: string;
|
||||
bubble?: boolean;
|
||||
services: IService[];
|
||||
}
|
||||
|
||||
@ -9,4 +10,7 @@ export interface IService {
|
||||
|
||||
description?: string;
|
||||
icon?: string;
|
||||
iconColor?: string;
|
||||
iconBG?: string;
|
||||
iconBubble?: boolean;
|
||||
}
|
||||
|
@ -3,13 +3,25 @@
|
||||
@tailwind utilities;
|
||||
|
||||
html {
|
||||
background-color: theme(colors.bg);
|
||||
background-color: theme(colors.slate.50);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
background-color: theme(colors.gray.950);
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
color: theme(colors.slate.200);
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: theme(fontSize.3xl);
|
||||
font-weight: theme(fontWeight.semibold);
|
||||
|
7
src/variables.ts
Normal file
7
src/variables.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export const PAGETITLE = "My Website";
|
||||
export const PAGEICON = "/logo.png";
|
||||
export const SHOWHEADER = true;
|
||||
export const SHOWHEADERLINE = true;
|
||||
export const SHOWHEADERTOP = false;
|
||||
export const CATEGORIES = "normal";
|
||||
export const THEME = "light";
|
@ -3,29 +3,16 @@ export default {
|
||||
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
||||
safelist: [
|
||||
/* Built from icon.tsx */
|
||||
"bg-blue-300",
|
||||
"bg-rose-300",
|
||||
"bg-green-300",
|
||||
"bg-red-300",
|
||||
"bg-yellow-300",
|
||||
"bg-cyan-300",
|
||||
"bg-pink-300",
|
||||
"bg-orange-300",
|
||||
"bg-sky-300",
|
||||
"bg-slate-300",
|
||||
"bg-emerald-300",
|
||||
"bg-zinc-300",
|
||||
"bg-neutral-300",
|
||||
"bg-amber-300",
|
||||
"bg-violet-300",
|
||||
{
|
||||
pattern:
|
||||
/bg-(black|white|slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(50|100|200|300|400|500|600|700|800|900|950)/,
|
||||
},
|
||||
"bg-transparent",
|
||||
"bg-black",
|
||||
"bg-white",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
bg: "#f8fafc",
|
||||
border: "#336699",
|
||||
},
|
||||
},
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user