Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .prettierrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
"trailingComma": "all",
"printWidth": 100,
"tabWidth": 2,
"plugins": ["prettier-plugin-tailwindcss"]
"plugins": ["prettier-plugin-tailwindcss"],
"tailwindStylesheet": "./apps/web/src/app/globals.css"
}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Embed Links — the minimum needed to post forms into a channel.
| Area | Technology |
|---|---|
| Frontend/SSR | Next.js 16 (App Router), TypeScript |
| Styling | Tailwind CSS 3 |
| Styling | Tailwind CSS 4 |
| Database | PostgreSQL + Prisma |
| Cache | Redis |
| Bot | Node.js + discord.js v14 |
Expand Down
5 changes: 2 additions & 3 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,13 @@
"zustand": "^5.0.15"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.3.3",
"@types/nodemailer": "^8.0.1",
"@types/qrcode": "^1.5.6",
"@types/react": "^19.3.0",
"@types/react-dom": "^19.3.0",
"autoprefixer": "^10.6.1",
"postcss": "^8.5.28",
"tailwindcss": "^3.4.17",
"tailwindcss-animate": "^1.0.7",
"tailwindcss": "^4.3.3",
"typescript": "^5.7.2",
"vitest": "^5.0.3"
}
Expand Down
3 changes: 1 addition & 2 deletions apps/web/postcss.config.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
"@tailwindcss/postcss": {},
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export default async function FormPreviewPage({

<div className="flex flex-wrap items-center justify-between gap-2 rounded-md border border-primary/30 bg-primary/5 px-3 py-2">
<span className="flex items-center gap-2 text-sm font-medium text-primary">
<span className="rounded-full bg-primary px-2 py-0.5 text-xs font-semibold uppercase tracking-wide text-primary-foreground">
<span className="rounded-full bg-primary px-2 py-0.5 text-xs font-semibold tracking-wide text-primary-foreground uppercase">
{dict.dashboard.preview}
</span>
{dict.dashboard.previewNotice}
Expand All @@ -82,7 +82,7 @@ export default async function FormPreviewPage({
{form.description && <p className="text-muted-foreground">{form.description}</p>}
</header>

<div className="rounded-lg border border-border bg-card p-6 shadow-sm">
<div className="rounded-lg border border-border bg-card p-6 shadow-xs">
<FormRenderer
slug={form.slug}
spec={spec}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export default async function SubmissionDetailPage({
<div className="grid gap-6 lg:grid-cols-[1fr_22rem]">
<div className="flex flex-col gap-6">
{submission.spec && (
<section className="rounded-lg border border-border bg-card p-6 shadow-sm">
<section className="rounded-lg border border-border bg-card p-6 shadow-xs">
<h2 className="mb-2 text-xs font-medium uppercase tracking-wide text-muted-foreground">
{t.answers}
</h2>
Expand All @@ -103,7 +103,7 @@ export default async function SubmissionDetailPage({
</section>
)}

<section className="rounded-lg border border-border bg-card p-6 shadow-sm">
<section className="rounded-lg border border-border bg-card p-6 shadow-xs">
<h2 className="mb-4 text-xs font-medium uppercase tracking-wide text-muted-foreground">
{t.timeline}
</h2>
Expand Down Expand Up @@ -159,7 +159,7 @@ export default async function SubmissionDetailPage({
t={t}
/>
) : (
<aside className="rounded-lg border border-border bg-card p-5 text-sm text-muted-foreground shadow-sm">
<aside className="rounded-lg border border-border bg-card p-5 text-sm text-muted-foreground shadow-xs">
{t.noPermReview}
</aside>
)}
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/app/f/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ function Shell({
<h1 className="font-heading text-3xl font-bold text-foreground">{title}</h1>
{description && <p className="text-muted-foreground">{description}</p>}
</header>
<div className="rounded-lg border border-border bg-card p-6 shadow-sm">{children}</div>
<div className="rounded-lg border border-border bg-card p-6 shadow-xs">{children}</div>
{poweredBy}
</main>
);
Expand Down
169 changes: 147 additions & 22 deletions apps/web/src/app/globals.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,150 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import 'tailwindcss';

@source '../../../../packages/ui/src/**/*.{ts,tsx}';

@custom-variant dark (&:is(.dark *));

@utility container {
margin-inline: auto;
padding-inline: 1.5rem;
@media (width >= --theme(--breakpoint-sm)) {
max-width: none;
}
@media (width >= 1180px) {
max-width: 1180px;
}
}

@theme {
--color-border: hsl(var(--border));
--color-input: hsl(var(--input));
--color-ring: hsl(var(--ring));
--color-background: hsl(var(--background));
--color-foreground: hsl(var(--foreground));

--color-primary: hsl(var(--primary));
--color-primary-foreground: hsl(var(--primary-foreground));

--color-secondary: hsl(var(--secondary));
--color-secondary-foreground: hsl(var(--secondary-foreground));

--color-destructive: hsl(var(--destructive));
--color-destructive-foreground: hsl(var(--destructive-foreground));

--color-muted: hsl(var(--muted));
--color-muted-foreground: hsl(var(--muted-foreground));

--color-accent: hsl(var(--accent));
--color-accent-foreground: hsl(var(--accent-foreground));

--color-popover: hsl(var(--popover));
--color-popover-foreground: hsl(var(--popover-foreground));

--color-card: hsl(var(--card));
--color-card-foreground: hsl(var(--card-foreground));

--color-chart-1: hsl(var(--chart-1));
--color-chart-2: hsl(var(--chart-2));
--color-chart-3: hsl(var(--chart-3));
--color-chart-4: hsl(var(--chart-4));
--color-chart-5: hsl(var(--chart-5));

--radius-lg: var(--radius);
--radius-md: calc(var(--radius) - 2px);
--radius-sm: calc(var(--radius) - 4px);

--font-sans: var(--font-inter), ui-sans-serif, system-ui, sans-serif;
--font-heading: var(--font-inter), ui-sans-serif, system-ui, sans-serif;
--font-mono: var(--font-jetbrains-mono), ui-monospace, monospace;

--shadow-card: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-card-hover: 0 8px 24px -8px rgb(0 0 0 / 0.12);

--animate-accordion-down: accordion-down 0.2s ease-out;
--animate-accordion-up: accordion-up 0.2s ease-out;
--animate-fade-in: fade-in 0.2s ease-out;
--animate-slide-up: slide-up 0.3s ease-out;

@keyframes accordion-down {
from {
height: 0;
}
to {
height: var(--radix-accordion-content-height);
}
}
@keyframes accordion-up {
from {
height: var(--radix-accordion-content-height);
}
to {
height: 0;
}
}
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slide-up {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
}

/*
The default border color has changed to `currentcolor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.

If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentcolor);
}

/* Tailwind v4 dropped `cursor: pointer` on buttons from its preflight; keep
the v3 behaviour so clickable controls still show the hand cursor. */
button:not(:disabled),
[role="button"]:not(:disabled) {
cursor: pointer;
}
}

/* ============================================================================
PWA safe-area insets. env() is 0 in a normal browser tab, so these are no-ops
there and only take effect in an installed/standalone PWA (viewport-fit=cover)
— keeping content clear of the notch (landscape) and the home indicator.
Uses logical `padding-inline` so it mirrors correctly under RTL.
============================================================================ */

@utility pwa-safe-x {
padding-inline-start: max(1.5rem, env(safe-area-inset-left));
padding-inline-end: max(1.5rem, env(safe-area-inset-right));
}

@utility pwa-safe-b {
padding-bottom: max(3rem, env(safe-area-inset-bottom));
}

@utility pwa-safe-top {
padding-top: env(safe-area-inset-top);
}

/* shadcn/ui theme — Neutral base + Emerald primary + Yellow charts. */
@layer base {
Expand Down Expand Up @@ -77,25 +221,6 @@
}
}

/* ============================================================================
PWA safe-area insets. env() is 0 in a normal browser tab, so these are no-ops
there and only take effect in an installed/standalone PWA (viewport-fit=cover)
— keeping content clear of the notch (landscape) and the home indicator.
Uses logical `padding-inline` so it mirrors correctly under RTL.
============================================================================ */
@layer utilities {
.pwa-safe-x {
padding-inline-start: max(1.5rem, env(safe-area-inset-left));
padding-inline-end: max(1.5rem, env(safe-area-inset-right));
}
.pwa-safe-b {
padding-bottom: max(3rem, env(safe-area-inset-bottom));
}
.pwa-safe-top {
padding-top: env(safe-area-inset-top);
}
}

/* ============================================================================
Legal pages (imprint / privacy / terms) — rendered Markdown. Mirrors the
msk-shop legal styling, ported to the HSL token system.
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/app/pricing/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export default async function PricingPage() {
}
>
{highlight && (
<Badge className="absolute -top-3 start-6 border-transparent bg-primary text-primary-foreground shadow-sm">
<Badge className="absolute -top-3 inset-s-6 border-transparent bg-primary text-primary-foreground shadow-xs">
{t.badgePopular}
</Badge>
)}
Expand Down
6 changes: 3 additions & 3 deletions apps/web/src/app/s/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export default async function SubmissionStatusPage({
</header>

{submission.events.length > 0 && (
<section className="rounded-lg border border-border bg-card p-6 shadow-sm">
<section className="rounded-lg border border-border bg-card p-6 shadow-xs">
<h2 className="mb-4 text-xs font-medium uppercase tracking-wide text-muted-foreground">
{t.activity}
</h2>
Expand All @@ -91,7 +91,7 @@ export default async function SubmissionStatusPage({
)}

{submission.spec && (
<section className="rounded-lg border border-border bg-card p-6 shadow-sm">
<section className="rounded-lg border border-border bg-card p-6 shadow-xs">
<h2 className="mb-2 text-xs font-medium uppercase tracking-wide text-muted-foreground">
{t.yourAnswers}
</h2>
Expand All @@ -104,7 +104,7 @@ export default async function SubmissionStatusPage({
</section>
)}

<section className="rounded-lg border border-border bg-card p-6 shadow-sm">
<section className="rounded-lg border border-border bg-card p-6 shadow-xs">
<SubmissionActions
id={submission.id}
canWithdraw={!TERMINAL.has(submission.status)}
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/board/kanban-board.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export function KanbanBoard({
{cards.map((s) => (
<div
key={s.id}
className="flex flex-col gap-2 rounded-lg border border-border bg-card p-3 shadow-sm transition-colors hover:border-primary/40"
className="flex flex-col gap-2 rounded-lg border border-border bg-card p-3 shadow-xs transition-colors hover:border-primary/40"
>
<div className="flex flex-col gap-0.5">
<span className="text-sm font-medium text-foreground">{s.applicant}</span>
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/bot/bot-config-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export function BotConfigForm({
setLocale(e.target.value);
setSaved(false);
}}
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
>
{LOCALE_OPTIONS.map((o) => (
<option key={o.value} value={o.value}>
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/confirm-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export function ConfirmDialog({
className="fixed inset-0 z-50 flex items-center justify-center p-4"
>
<div
className="absolute inset-0 bg-black/60 backdrop-blur-sm"
className="absolute inset-0 bg-black/60 backdrop-blur-xs"
onClick={busy ? undefined : onCancel}
/>
<div className="relative z-10 w-full max-w-md rounded-lg border border-border bg-card p-6 shadow-xl">
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/dashboard/nav-tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function NavTabs({ tabs }: { tabs: NavTab[] }) {
const pathname = usePathname();

return (
<nav className="flex gap-1 overflow-x-auto overflow-y-hidden border-b border-border [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
<nav className="flex gap-1 overflow-x-auto overflow-y-hidden border-b border-border scrollbar-none [&::-webkit-scrollbar]:hidden">
{tabs.map((tab) => {
const active = tab.prefix
? pathname.startsWith(tab.href)
Expand Down
6 changes: 3 additions & 3 deletions apps/web/src/components/form/date-field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ export function DateField({
const hours = Array.from({ length: 24 }, (_, i) => i);
const minutes = Array.from({ length: 60 }, (_, i) => i);
const selectClass =
"h-9 rounded-md border border-input bg-background px-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring";
"h-9 rounded-md border border-input bg-background px-2 text-sm text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring";

return (
<div ref={ref} className="relative">
Expand All @@ -171,7 +171,7 @@ export function DateField({
aria-haspopup="dialog"
aria-expanded={open}
onClick={() => setOpen((o) => !o)}
className={`flex h-10 w-full items-center justify-between rounded-md border bg-background px-3 py-2 text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 ${
className={`flex h-10 w-full items-center justify-between rounded-md border bg-background px-3 py-2 text-sm transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 ${
invalid ? "border-destructive" : "border-input"
} ${displayValue ? "text-foreground" : "text-muted-foreground"}`}
>
Expand All @@ -186,7 +186,7 @@ export function DateField({
{open && (
<div
role="dialog"
className="absolute start-0 z-50 mt-1.5 w-72 rounded-lg border border-border bg-popover p-3 text-popover-foreground shadow-md"
className="absolute inset-s-0 z-50 mt-1.5 w-72 rounded-lg border border-border bg-popover p-3 text-popover-foreground shadow-md"
>
{showCalendar && (
<>
Expand Down
Loading
Loading