Reorganizes and optimizes shared CSS components

Consolidates UI components into a centralized components.css file
Removes duplicate styles across multiple CSS files
Adds utility classes in a new utilities.css file
Reduces overall CSS complexity and improves maintainability

Removes quick-stats.css and redistributes its styles
Updates layout and stylesheet references accordingly
This commit is contained in:
Janus C. H. Knudsen 2026-01-13 00:09:46 +01:00
parent 15579acba8
commit 2e6207bb0b
33 changed files with 971 additions and 796 deletions

View file

@ -6,7 +6,8 @@
"Bash(dotnet build:*)", "Bash(dotnet build:*)",
"Bash(find:*)", "Bash(find:*)",
"Bash(tree:*)", "Bash(tree:*)",
"Bash(npm run analyze-css:*)" "Bash(npm run analyze-css:*)",
"Bash(node:*)"
] ]
} }
} }

View file

@ -9,6 +9,6 @@
], ],
"settings": { "settings": {
"liveServer.settings.port": 5501, "liveServer.settings.port": 5501,
"liveServer.settings.multiRootWorkspaceName": "PlanTempus" "liveServer.settings.multiRootWorkspaceName": "Calendar"
} }
} }

View file

@ -1,7 +1,5 @@
<swp-card> <swp-card>
<swp-card-header> <swp-section-label localize="cash.approval.title">Afslut dagen</swp-section-label>
<swp-card-title localize="cash.approval.title">Afslut dagen</swp-card-title>
</swp-card-header>
<swp-card-content> <swp-card-content>
<swp-approval-grid> <swp-approval-grid>
<swp-form-field> <swp-form-field>

View file

@ -1,7 +1,5 @@
<swp-card> <swp-card>
<swp-card-header> <swp-section-label localize="cash.balance.title">Kontanter i kassen</swp-section-label>
<swp-card-title localize="cash.balance.title">Kontanter i kassen</swp-card-title>
</swp-card-header>
<swp-card-content> <swp-card-content>
<swp-calc-row> <swp-calc-row>
<swp-calc-label> <swp-calc-label>

View file

@ -1,7 +1,5 @@
<swp-card> <swp-card>
<swp-card-header> <swp-section-label localize="cash.period.title">Periodeoplysninger</swp-section-label>
<swp-card-title localize="cash.period.title">Periodeoplysninger</swp-card-title>
</swp-card-header>
<swp-card-content> <swp-card-content>
<swp-period-display> <swp-period-display>
<swp-period-label localize="cash.period.dateRange">Periode</swp-period-label> <swp-period-label localize="cash.period.dateRange">Periode</swp-period-label>

View file

@ -1,8 +1,5 @@
<swp-card> <swp-card>
<swp-card-header> <swp-section-label localize="cash.revenue.title">Periodens omsætning</swp-section-label>
<swp-card-title localize="cash.revenue.title">Periodens omsætning</swp-card-title>
<swp-card-action localize="cash.revenue.subtitle">Systemtal vs. kontrol</swp-card-action>
</swp-card-header>
<swp-card-content> <swp-card-content>
<swp-data-table> <swp-data-table>
<swp-data-header> <swp-data-header>

View file

@ -1,7 +1,5 @@
<swp-card> <swp-card>
<swp-card-header> <swp-section-label localize="cash.note.title">Note til afstemning</swp-section-label>
<swp-card-title localize="cash.note.title">Note til afstemning</swp-card-title>
</swp-card-header>
<swp-card-content> <swp-card-content>
<swp-note-field> <swp-note-field>
<textarea placeholder="Beskriv evt. årsag til difference..."></textarea> <textarea placeholder="Beskriv evt. årsag til difference..."></textarea>

View file

@ -1,12 +1,7 @@
@model PlanTempus.Application.Features.Dashboard.Components.AttentionListViewModel @model PlanTempus.Application.Features.Dashboard.Components.AttentionListViewModel
<swp-card data-key="@Model.Key"> <swp-card data-key="@Model.Key">
<swp-card-header> <swp-section-label>@Model.Title</swp-section-label>
<swp-card-title>
<i class="ph ph-warning-circle"></i>
@Model.Title
</swp-card-title>
</swp-card-header>
<swp-card-content> <swp-card-content>
<swp-attention-list> <swp-attention-list>
@foreach (var attentionKey in Model.AttentionKeys) @foreach (var attentionKey in Model.AttentionKeys)

View file

@ -1,13 +1,10 @@
@model PlanTempus.Application.Features.Dashboard.Components.BookingListViewModel @model PlanTempus.Application.Features.Dashboard.Components.BookingListViewModel
<swp-card data-key="@Model.Key"> <swp-card data-key="@Model.Key">
<swp-card-header> <swp-section-header>
<swp-card-title> <swp-section-label>@Model.Title</swp-section-label>
<i class="ph ph-calendar-check"></i> <swp-section-action localize="dashboard.bookings.viewAll">Se alle</swp-section-action>
@Model.Title </swp-section-header>
</swp-card-title>
<swp-card-action localize="dashboard.bookings.viewAll">Se alle</swp-card-action>
</swp-card-header>
<swp-current-time> <swp-current-time>
<i class="ph ph-clock"></i> <i class="ph ph-clock"></i>
<span><span localize="dashboard.bookings.currentTime">Nu:</span> <swp-time>@Model.CurrentTime</swp-time></span> <span><span localize="dashboard.bookings.currentTime">Nu:</span> <swp-time>@Model.CurrentTime</swp-time></span>

View file

@ -1,13 +1,10 @@
@model PlanTempus.Application.Features.Dashboard.Components.NotificationListViewModel @model PlanTempus.Application.Features.Dashboard.Components.NotificationListViewModel
<swp-card data-key="@Model.Key"> <swp-card data-key="@Model.Key">
<swp-card-header> <swp-section-header>
<swp-card-title> <swp-section-label>@Model.Title</swp-section-label>
<i class="ph ph-bell"></i> <swp-section-action>@Model.ActionText</swp-section-action>
@Model.Title </swp-section-header>
</swp-card-title>
<swp-card-action>@Model.ActionText</swp-card-action>
</swp-card-header>
<swp-card-content> <swp-card-content>
<swp-notification-list> <swp-notification-list>
@foreach (var notificationKey in Model.NotificationKeys) @foreach (var notificationKey in Model.NotificationKeys)

View file

@ -1,12 +1,7 @@
@model PlanTempus.Application.Features.Dashboard.Components.QuickStatListViewModel @model PlanTempus.Application.Features.Dashboard.Components.QuickStatListViewModel
<swp-card data-key="@Model.Key"> <swp-card data-key="@Model.Key">
<swp-card-header> <swp-section-label>@Model.Title</swp-section-label>
<swp-card-title>
<i class="ph ph-@Model.Icon"></i>
@Model.Title
</swp-card-title>
</swp-card-header>
<swp-card-content> <swp-card-content>
<swp-quick-stats> <swp-quick-stats>
@foreach (var statKey in Model.StatKeys) @foreach (var statKey in Model.StatKeys)

View file

@ -50,9 +50,7 @@
<!-- Quick Actions --> <!-- Quick Actions -->
<swp-card> <swp-card>
<swp-card-header> <swp-section-label localize="dashboard.quickActions.title">Hurtige handlinger</swp-section-label>
<swp-card-title localize="dashboard.quickActions.title">Hurtige handlinger</swp-card-title>
</swp-card-header>
<swp-card-content> <swp-card-content>
<swp-quick-actions> <swp-quick-actions>
<swp-quick-action-btn> <swp-quick-action-btn>

View file

@ -7,8 +7,12 @@
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@@phosphor-icons/web@@2.1.2/src/regular/style.css" /> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@@phosphor-icons/web@@2.1.2/src/regular/style.css" />
<!-- Design System --> <!-- Design System -->
<link rel="stylesheet" href="~/css/design-tokens.css">
<link rel="stylesheet" href="~/css/design-system.css"> <link rel="stylesheet" href="~/css/design-system.css">
<link rel="stylesheet" href="~/css/base.css"> <link rel="stylesheet" href="~/css/base.css">
<link rel="stylesheet" href="~/css/utilities.css">
<!-- Shared UI Components -->
<link rel="stylesheet" href="~/css/components.css">
<!-- Layout Components --> <!-- Layout Components -->
<link rel="stylesheet" href="~/css/app-layout.css"> <link rel="stylesheet" href="~/css/app-layout.css">
<link rel="stylesheet" href="~/css/sidebar.css"> <link rel="stylesheet" href="~/css/sidebar.css">
@ -21,7 +25,6 @@
<link rel="stylesheet" href="~/css/bookings.css"> <link rel="stylesheet" href="~/css/bookings.css">
<link rel="stylesheet" href="~/css/notifications.css"> <link rel="stylesheet" href="~/css/notifications.css">
<link rel="stylesheet" href="~/css/attentions.css"> <link rel="stylesheet" href="~/css/attentions.css">
<link rel="stylesheet" href="~/css/quick-stats.css">
<link rel="stylesheet" href="~/css/waitlist.css"> <link rel="stylesheet" href="~/css/waitlist.css">
<link rel="stylesheet" href="~/css/tabs.css"> <link rel="stylesheet" href="~/css/tabs.css">
<link rel="stylesheet" href="~/css/controls.css"> <link rel="stylesheet" href="~/css/controls.css">

View file

@ -152,19 +152,19 @@
<div class="summary"> <div class="summary">
<div class="stat-card"> <div class="stat-card">
<div class="stat-label">Total CSS Size</div> <div class="stat-label">Total CSS Size</div>
<div class="stat-value">132.81 KB</div> <div class="stat-value">134.17 KB</div>
</div> </div>
<div class="stat-card info"> <div class="stat-card info">
<div class="stat-label">CSS Files</div> <div class="stat-label">CSS Files</div>
<div class="stat-value">21</div> <div class="stat-value">22</div>
</div> </div>
<div class="stat-card info"> <div class="stat-card info">
<div class="stat-label">Total Lines</div> <div class="stat-label">Total Lines</div>
<div class="stat-value">6.033</div> <div class="stat-value">6.124</div>
</div> </div>
<div class="stat-card warning"> <div class="stat-card warning">
<div class="stat-label">Unused CSS Rules</div> <div class="stat-label">Unused CSS Rules</div>
<div class="stat-value">61</div> <div class="stat-value">87</div>
</div> </div>
</div> </div>
@ -186,34 +186,44 @@
<tr> <tr>
<td><strong>auth.css</strong></td> <td><strong>auth.css</strong></td>
<td>23.66 KB</td> <td>20.41 KB</td>
<td>1144</td> <td>994</td>
<td>169</td> <td>145</td>
<td>173</td> <td>149</td>
<td>571</td> <td>488</td>
<td>46</td> <td>43</td>
</tr> </tr>
<tr> <tr>
<td><strong>cash.css</strong></td> <td><strong>cash.css</strong></td>
<td>19.38 KB</td> <td>16.75 KB</td>
<td>898</td> <td>782</td>
<td>132</td> <td>114</td>
<td>135</td> <td>117</td>
<td>415</td> <td>359</td>
<td>42</td> <td>39</td>
</tr> </tr>
<tr> <tr>
<td><strong>employees.css</strong></td> <td><strong>employees.css</strong></td>
<td>15.55 KB</td> <td>15.02 KB</td>
<td>722</td> <td>694</td>
<td>105</td> <td>100</td>
<td>108</td> <td>103</td>
<td>345</td> <td>327</td>
<td>37</td> <td>37</td>
</tr> </tr>
<tr>
<td><strong>components.css</strong></td>
<td>9.23 KB</td>
<td>426</td>
<td>63</td>
<td>63</td>
<td>185</td>
<td>39</td>
</tr>
<tr> <tr>
<td><strong>design-tokens.css</strong></td> <td><strong>design-tokens.css</strong></td>
<td>9.02 KB</td> <td>9.02 KB</td>
@ -226,12 +236,12 @@
<tr> <tr>
<td><strong>account.css</strong></td> <td><strong>account.css</strong></td>
<td>8.83 KB</td> <td>7.31 KB</td>
<td>402</td> <td>335</td>
<td>60</td> <td>47</td>
<td>63</td> <td>50</td>
<td>173</td> <td>146</td>
<td>31</td> <td>30</td>
</tr> </tr>
<tr> <tr>
@ -245,13 +255,13 @@
</tr> </tr>
<tr> <tr>
<td><strong>page.css</strong></td> <td><strong>stats.css</strong></td>
<td>6.00 KB</td> <td>5.87 KB</td>
<td>276</td> <td>262</td>
<td>38</td> <td>34</td>
<td>40</td> <td>36</td>
<td>117</td> <td>93</td>
<td>35</td> <td>30</td>
</tr> </tr>
<tr> <tr>
@ -265,29 +275,29 @@
</tr> </tr>
<tr> <tr>
<td><strong>waitlist.css</strong></td> <td><strong>page.css</strong></td>
<td>5.55 KB</td> <td>5.07 KB</td>
<td>251</td> <td>231</td>
<td>30</td>
<td>30</td>
<td>131</td>
<td>31</td> <td>31</td>
<td>33</td>
<td>92</td>
<td>33</td>
</tr> </tr>
<tr> <tr>
<td><strong>stats.css</strong></td> <td><strong>waitlist.css</strong></td>
<td>5.18 KB</td> <td>4.63 KB</td>
<td>232</td> <td>211</td>
<td>30</td> <td>25</td>
<td>32</td> <td>25</td>
<td>78</td> <td>101</td>
<td>27</td> <td>28</td>
</tr> </tr>
<tr> <tr>
<td><strong>bookings.css</strong></td> <td><strong>bookings.css</strong></td>
<td>4.27 KB</td> <td>4.34 KB</td>
<td>176</td> <td>177</td>
<td>28</td> <td>28</td>
<td>28</td> <td>28</td>
<td>75</td> <td>75</td>
@ -364,6 +374,16 @@
<td>8</td> <td>8</td>
</tr> </tr>
<tr>
<td><strong>utilities.css</strong></td>
<td>2.03 KB</td>
<td>119</td>
<td>24</td>
<td>24</td>
<td>44</td>
<td>9</td>
</tr>
<tr> <tr>
<td><strong>notifications.css</strong></td> <td><strong>notifications.css</strong></td>
<td>1.67 KB</td> <td>1.67 KB</td>
@ -384,16 +404,6 @@
<td>6</td> <td>6</td>
</tr> </tr>
<tr>
<td><strong>quick-stats.css</strong></td>
<td>0.88 KB</td>
<td>39</td>
<td>4</td>
<td>4</td>
<td>15</td>
<td>11</td>
</tr>
</tbody> </tbody>
</table> </table>
</section> </section>
@ -422,6 +432,27 @@
</div> </div>
<div class="file-detail">
<h3>utilities.css</h3>
<p>
<span class="badge badge-warning">
23 unused rules
</span>
<span style="margin-left: 10px; color: #666;">
Original: 1001 | After purge: 784
</span>
</p>
<details>
<summary>Show unused selectors</summary>
<div class="rejected-list">
.flex-center<br>.flex-between<br>.flex-start<br>.flex-end<br>.flex-column<br>.flex-1<br>.flex-shrink-0<br>.text-truncate<br>.text-center<br>.text-right<br>.text-secondary<br>.text-muted<br>.text-mono<br>.gap-2<br>.gap-3<br>.gap-4<br>.gap-6<br>.gap-8<br>.mt-auto<br>.mb-0<br>.sr-only<br>.w-full<br>.min-w-0
</div>
</details>
</div>
<div class="file-detail"> <div class="file-detail">
<h3>design-system.css</h3> <h3>design-system.css</h3>
<p> <p>
@ -443,6 +474,27 @@
</div> </div>
<div class="file-detail">
<h3>components.css</h3>
<p>
<span class="badge badge-success">
7 unused rules
</span>
<span style="margin-left: 10px; color: #666;">
Original: 8905 | After purge: 8780
</span>
</p>
<details>
<summary>Show unused selectors</summary>
<div class="rejected-list">
swp-btn.social<br>swp-btn.social:hover<br>swp-btn.social img<br>swp-plan-badge.free<br>swp-avatar.size-xs<br>swp-avatar.size-sm<br>swp-avatar.size-lg
</div>
</details>
</div>
<div class="file-detail"> <div class="file-detail">
<h3>bookings.css</h3> <h3>bookings.css</h3>
<p> <p>
@ -450,7 +502,7 @@
6 unused rules 6 unused rules
</span> </span>
<span style="margin-left: 10px; color: #666;"> <span style="margin-left: 10px; color: #666;">
Original: 3924 | After purge: 3756 Original: 3998 | After purge: 3830
</span> </span>
</p> </p>
@ -507,27 +559,6 @@ swp-booking-status.in-progress
</div> </div>
<div class="file-detail">
<h3>auth.css</h3>
<p>
<span class="badge badge-success">
4 unused rules
</span>
<span style="margin-left: 10px; color: #666;">
Original: 23887 | After purge: 23816
</span>
</p>
<details>
<summary>Show unused selectors</summary>
<div class="rejected-list">
swp-btn.social<br>swp-btn.social:hover<br>swp-btn.social img<br>swp-plan-badge.free
</div>
</details>
</div>
<div class="file-detail"> <div class="file-detail">
<h3>stats.css</h3> <h3>stats.css</h3>
<p> <p>
@ -535,7 +566,7 @@ swp-booking-status.in-progress
3 unused rules 3 unused rules
</span> </span>
<span style="margin-left: 10px; color: #666;"> <span style="margin-left: 10px; color: #666;">
Original: 5298 | After purge: 5229 Original: 6010 | After purge: 5941
</span> </span>
</p> </p>
@ -557,7 +588,7 @@ swp-stat-card.red swp-stat-value
2 unused rules 2 unused rules
</span> </span>
<span style="margin-left: 10px; color: #666;"> <span style="margin-left: 10px; color: #666;">
Original: 8829 | After purge: 8777 Original: 7277 | After purge: 7225
</span> </span>
</p> </p>
@ -599,7 +630,7 @@ swp-stat-card.red swp-stat-value
1 unused rules 1 unused rules
</span> </span>
<span style="margin-left: 10px; color: #666;"> <span style="margin-left: 10px; color: #666;">
Original: 19816 | After purge: 19778 Original: 17121 | After purge: 17083
</span> </span>
</p> </p>
@ -620,7 +651,7 @@ swp-stat-card.red swp-stat-value
0 unused rules 0 unused rules
</span> </span>
<span style="margin-left: 10px; color: #666;"> <span style="margin-left: 10px; color: #666;">
Original: 5686 | After purge: 5686 Original: 4737 | After purge: 4737
</span> </span>
</p> </p>
<p style="color: #2e7d32; margin-top: 10px;">✅ No unused CSS found!</p> <p style="color: #2e7d32; margin-top: 10px;">✅ No unused CSS found!</p>
@ -665,19 +696,6 @@ swp-stat-card.red swp-stat-value
<p style="color: #2e7d32; margin-top: 10px;">✅ No unused CSS found!</p> <p style="color: #2e7d32; margin-top: 10px;">✅ No unused CSS found!</p>
</div> </div>
<div class="file-detail">
<h3>quick-stats.css</h3>
<p>
<span class="badge badge-success">
0 unused rules
</span>
<span style="margin-left: 10px; color: #666;">
Original: 899 | After purge: 899
</span>
</p>
<p style="color: #2e7d32; margin-top: 10px;">✅ No unused CSS found!</p>
</div>
<div class="file-detail"> <div class="file-detail">
<h3>page.css</h3> <h3>page.css</h3>
<p> <p>
@ -685,7 +703,7 @@ swp-stat-card.red swp-stat-value
0 unused rules 0 unused rules
</span> </span>
<span style="margin-left: 10px; color: #666;"> <span style="margin-left: 10px; color: #666;">
Original: 6140 | After purge: 6140 Original: 5193 | After purge: 5193
</span> </span>
</p> </p>
<p style="color: #2e7d32; margin-top: 10px;">✅ No unused CSS found!</p> <p style="color: #2e7d32; margin-top: 10px;">✅ No unused CSS found!</p>
@ -711,7 +729,7 @@ swp-stat-card.red swp-stat-value
0 unused rules 0 unused rules
</span> </span>
<span style="margin-left: 10px; color: #666;"> <span style="margin-left: 10px; color: #666;">
Original: 15923 | After purge: 15923 Original: 15383 | After purge: 15383
</span> </span>
</p> </p>
<p style="color: #2e7d32; margin-top: 10px;">✅ No unused CSS found!</p> <p style="color: #2e7d32; margin-top: 10px;">✅ No unused CSS found!</p>
@ -743,6 +761,19 @@ swp-stat-card.red swp-stat-value
<p style="color: #2e7d32; margin-top: 10px;">✅ No unused CSS found!</p> <p style="color: #2e7d32; margin-top: 10px;">✅ No unused CSS found!</p>
</div> </div>
<div class="file-detail">
<h3>auth.css</h3>
<p>
<span class="badge badge-success">
0 unused rules
</span>
<span style="margin-left: 10px; color: #666;">
Original: 20895 | After purge: 20895
</span>
</p>
<p style="color: #2e7d32; margin-top: 10px;">✅ No unused CSS found!</p>
</div>
<div class="file-detail"> <div class="file-detail">
<h3>app-layout.css</h3> <h3>app-layout.css</h3>
<p> <p>
@ -769,7 +800,7 @@ swp-stat-card.red swp-stat-value
</ul> </ul>
</section> </section>
<p class="timestamp">Report generated: 12.1.2026, 21.57.11</p> <p class="timestamp">Report generated: 12.1.2026, 23.25.14</p>
</div> </div>
</body> </body>
</html> </html>

View file

@ -1,26 +1,26 @@
{ {
"account.css": { "account.css": {
"lines": 402, "lines": 335,
"size": "8.83 KB", "size": "7.31 KB",
"sizeBytes": 9037, "sizeBytes": 7485,
"rules": 60, "rules": 47,
"selectors": 63, "selectors": 50,
"properties": 173, "properties": 146,
"uniqueColors": 13, "uniqueColors": 12,
"colors": [ "colors": [
"var(--color-text)", "var(--color-text)",
"var(--color-teal)", "var(--color-teal)",
"var(--color-background-alt)",
"var(--color-text-secondary)",
"var(--color-surface)", "var(--color-surface)",
"var(--color-purple)",
"var(--color-amber)",
"rgba(0, 0, 0, 0.08)", "rgba(0, 0, 0, 0.08)",
"var(--color-background-alt)",
"var(--color-border)", "var(--color-border)",
"var(--color-blue)" "var(--color-blue)",
"var(--color-text-secondary)",
"var(--color-background-hover)",
"var(--color-green)"
], ],
"mediaQueries": 3, "mediaQueries": 3,
"cssVariables": 31 "cssVariables": 30
}, },
"app-layout.css": { "app-layout.css": {
"lines": 51, "lines": 51,
@ -59,13 +59,13 @@
"cssVariables": 15 "cssVariables": 15
}, },
"auth.css": { "auth.css": {
"lines": 1144, "lines": 994,
"size": "23.66 KB", "size": "20.41 KB",
"sizeBytes": 24232, "sizeBytes": 20895,
"rules": 169, "rules": 145,
"selectors": 173, "selectors": 149,
"properties": 571, "properties": 488,
"uniqueColors": 17, "uniqueColors": 15,
"colors": [ "colors": [
"var(--color-teal)", "var(--color-teal)",
"#00695c", "#00695c",
@ -74,12 +74,12 @@
"var(--color-background)", "var(--color-background)",
"var(--color-text)", "var(--color-text)",
"var(--color-text-secondary)", "var(--color-text-secondary)",
"var(--color-surface)", "var(--color-purple)",
"var(--color-border)", "var(--color-green)",
"var(--color-purple)" "var(--color-surface)"
], ],
"mediaQueries": 3, "mediaQueries": 3,
"cssVariables": 46 "cssVariables": 43
}, },
"base.css": { "base.css": {
"lines": 119, "lines": 119,
@ -99,9 +99,9 @@
"cssVariables": 8 "cssVariables": 8
}, },
"bookings.css": { "bookings.css": {
"lines": 176, "lines": 177,
"size": "4.27 KB", "size": "4.34 KB",
"sizeBytes": 4369, "sizeBytes": 4443,
"rules": 28, "rules": 28,
"selectors": 28, "selectors": 28,
"properties": 75, "properties": 75,
@ -122,13 +122,13 @@
"cssVariables": 27 "cssVariables": 27
}, },
"cash.css": { "cash.css": {
"lines": 898, "lines": 782,
"size": "19.38 KB", "size": "16.75 KB",
"sizeBytes": 19850, "sizeBytes": 17155,
"rules": 132, "rules": 114,
"selectors": 135, "selectors": 117,
"properties": 415, "properties": 359,
"uniqueColors": 14, "uniqueColors": 12,
"colors": [ "colors": [
"var(--color-surface)", "var(--color-surface)",
"var(--color-border)", "var(--color-border)",
@ -142,7 +142,30 @@
"var(--color-background-hover)" "var(--color-background-hover)"
], ],
"mediaQueries": 3, "mediaQueries": 3,
"cssVariables": 42 "cssVariables": 39
},
"components.css": {
"lines": 426,
"size": "9.23 KB",
"sizeBytes": 9447,
"rules": 63,
"selectors": 63,
"properties": 185,
"uniqueColors": 16,
"colors": [
"var(--color-teal)",
"#00796b",
"var(--color-border)",
"var(--color-surface)",
"var(--color-text)",
"var(--color-background-hover)",
"var(--color-text-secondary)",
"var(--color-purple)",
"var(--color-background)",
"var(--color-text-muted)"
],
"mediaQueries": 0,
"cssVariables": 39
}, },
"controls.css": { "controls.css": {
"lines": 149, "lines": 149,
@ -251,12 +274,12 @@
"cssVariables": 32 "cssVariables": 32
}, },
"employees.css": { "employees.css": {
"lines": 722, "lines": 694,
"size": "15.55 KB", "size": "15.02 KB",
"sizeBytes": 15923, "sizeBytes": 15383,
"rules": 105, "rules": 100,
"selectors": 108, "selectors": 103,
"properties": 345, "properties": 327,
"uniqueColors": 15, "uniqueColors": 15,
"colors": [ "colors": [
"var(--color-text-secondary)", "var(--color-text-secondary)",
@ -264,11 +287,11 @@
"var(--color-border)", "var(--color-border)",
"var(--color-teal)", "var(--color-teal)",
"var(--color-surface)", "var(--color-surface)",
"rgba(0, 0, 0, 0.08)",
"var(--color-background-alt)", "var(--color-background-alt)",
"var(--color-background-hover)", "var(--color-background-hover)",
"var(--color-purple)", "var(--color-purple)",
"var(--color-blue)" "var(--color-blue)",
"var(--color-amber)"
], ],
"mediaQueries": 3, "mediaQueries": 3,
"cssVariables": 37 "cssVariables": 37
@ -291,41 +314,25 @@
"cssVariables": 8 "cssVariables": 8
}, },
"page.css": { "page.css": {
"lines": 276, "lines": 231,
"size": "6.00 KB", "size": "5.07 KB",
"sizeBytes": 6141, "sizeBytes": 5194,
"rules": 38, "rules": 31,
"selectors": 40, "selectors": 33,
"properties": 117, "properties": 92,
"uniqueColors": 8, "uniqueColors": 8,
"colors": [ "colors": [
"var(--color-surface)", "var(--color-surface)",
"var(--color-border)", "var(--color-border)",
"var(--color-text)", "var(--color-text)",
"var(--color-text-secondary)", "var(--color-text-secondary)",
"var(--color-teal)",
"var(--color-purple)", "var(--color-purple)",
"var(--color-teal)",
"var(--color-background)", "var(--color-background)",
"var(--color-background-hover)" "var(--color-background-hover)"
], ],
"mediaQueries": 2, "mediaQueries": 2,
"cssVariables": 35 "cssVariables": 33
},
"quick-stats.css": {
"lines": 39,
"size": "0.88 KB",
"sizeBytes": 899,
"rules": 4,
"selectors": 4,
"properties": 15,
"uniqueColors": 3,
"colors": [
"var(--color-background-alt)",
"var(--color-text)",
"var(--color-text-secondary)"
],
"mediaQueries": 0,
"cssVariables": 11
}, },
"sidebar.css": { "sidebar.css": {
"lines": 247, "lines": 247,
@ -350,13 +357,13 @@
"cssVariables": 24 "cssVariables": 24
}, },
"stats.css": { "stats.css": {
"lines": 232, "lines": 262,
"size": "5.18 KB", "size": "5.87 KB",
"sizeBytes": 5301, "sizeBytes": 6013,
"rules": 30, "rules": 34,
"selectors": 32, "selectors": 36,
"properties": 78, "properties": 93,
"uniqueColors": 14, "uniqueColors": 15,
"colors": [ "colors": [
"var(--color-surface)", "var(--color-surface)",
"var(--color-border)", "var(--color-border)",
@ -370,7 +377,7 @@
"var(--color-purple)" "var(--color-purple)"
], ],
"mediaQueries": 2, "mediaQueries": 2,
"cssVariables": 27 "cssVariables": 30
}, },
"tabs.css": { "tabs.css": {
"lines": 95, "lines": 95,
@ -412,14 +419,29 @@
"mediaQueries": 0, "mediaQueries": 0,
"cssVariables": 20 "cssVariables": 20
}, },
"utilities.css": {
"lines": 119,
"size": "2.03 KB",
"sizeBytes": 2080,
"rules": 24,
"selectors": 24,
"properties": 44,
"uniqueColors": 2,
"colors": [
"var(--color-text-secondary)",
"var(--color-text-muted)"
],
"mediaQueries": 0,
"cssVariables": 9
},
"waitlist.css": { "waitlist.css": {
"lines": 251, "lines": 211,
"size": "5.55 KB", "size": "4.63 KB",
"sizeBytes": 5686, "sizeBytes": 4737,
"rules": 30, "rules": 25,
"selectors": 30, "selectors": 25,
"properties": 131, "properties": 101,
"uniqueColors": 9, "uniqueColors": 8,
"colors": [ "colors": [
"var(--color-surface)", "var(--color-surface)",
"var(--color-border)", "var(--color-border)",
@ -428,10 +450,9 @@
"var(--color-text)", "var(--color-text)",
"var(--color-background-alt)", "var(--color-background-alt)",
"var(--color-background)", "var(--color-background)",
"var(--color-amber)", "var(--color-amber)"
"var(--color-background-hover)"
], ],
"mediaQueries": 0, "mediaQueries": 0,
"cssVariables": 31 "cssVariables": 28
} }
} }

View file

@ -1,19 +1,49 @@
{ {
"summary": { "summary": {
"totalFiles": 21, "totalFiles": 22,
"totalOriginalSize": 132731, "totalOriginalSize": 132849,
"totalPurgedSize": 131811, "totalPurgedSize": 131658,
"totalRejected": 61, "totalRejected": 87,
"percentageRemoved": "0.05%", "percentageRemoved": "0.07%",
"potentialSavings": 920 "potentialSavings": 1191
}, },
"fileDetails": { "fileDetails": {
"waitlist.css": { "waitlist.css": {
"originalSize": 5686, "originalSize": 4737,
"purgedSize": 5686, "purgedSize": 4737,
"rejectedCount": 0, "rejectedCount": 0,
"rejected": [] "rejected": []
}, },
"utilities.css": {
"originalSize": 1001,
"purgedSize": 784,
"rejectedCount": 23,
"rejected": [
".flex-center",
".flex-between",
".flex-start",
".flex-end",
".flex-column",
".flex-1",
".flex-shrink-0",
".text-truncate",
".text-center",
".text-right",
".text-secondary",
".text-muted",
".text-mono",
".gap-2",
".gap-3",
".gap-4",
".gap-6",
".gap-8",
".mt-auto",
".mb-0",
".sr-only",
".w-full",
".min-w-0"
]
},
"topbar.css": { "topbar.css": {
"originalSize": 3885, "originalSize": 3885,
"purgedSize": 3885, "purgedSize": 3885,
@ -27,8 +57,8 @@
"rejected": [] "rejected": []
}, },
"stats.css": { "stats.css": {
"originalSize": 5298, "originalSize": 6010,
"purgedSize": 5229, "purgedSize": 5941,
"rejectedCount": 3, "rejectedCount": 3,
"rejected": [ "rejected": [
"swp-stat-trend.up", "swp-stat-trend.up",
@ -42,15 +72,9 @@
"rejectedCount": 0, "rejectedCount": 0,
"rejected": [] "rejected": []
}, },
"quick-stats.css": {
"originalSize": 899,
"purgedSize": 899,
"rejectedCount": 0,
"rejected": []
},
"page.css": { "page.css": {
"originalSize": 6140, "originalSize": 5193,
"purgedSize": 6140, "purgedSize": 5193,
"rejectedCount": 0, "rejectedCount": 0,
"rejected": [] "rejected": []
}, },
@ -61,8 +85,8 @@
"rejected": [] "rejected": []
}, },
"employees.css": { "employees.css": {
"originalSize": 15923, "originalSize": 15383,
"purgedSize": 15923, "purgedSize": 15383,
"rejectedCount": 0, "rejectedCount": 0,
"rejected": [] "rejected": []
}, },
@ -135,17 +159,31 @@
"rejectedCount": 0, "rejectedCount": 0,
"rejected": [] "rejected": []
}, },
"components.css": {
"originalSize": 8905,
"purgedSize": 8780,
"rejectedCount": 7,
"rejected": [
"swp-btn.social",
"swp-btn.social:hover",
"swp-btn.social img",
"swp-plan-badge.free",
"swp-avatar.size-xs",
"swp-avatar.size-sm",
"swp-avatar.size-lg"
]
},
"cash.css": { "cash.css": {
"originalSize": 19816, "originalSize": 17121,
"purgedSize": 19778, "purgedSize": 17083,
"rejectedCount": 1, "rejectedCount": 1,
"rejected": [ "rejected": [
"swp-cash-stat.user swp-cash-stat-value" "swp-cash-stat.user swp-cash-stat-value"
] ]
}, },
"bookings.css": { "bookings.css": {
"originalSize": 3924, "originalSize": 3998,
"purgedSize": 3756, "purgedSize": 3830,
"rejectedCount": 6, "rejectedCount": 6,
"rejected": [ "rejected": [
"swp-booking-item.inprogress", "swp-booking-item.inprogress",
@ -168,15 +206,10 @@
] ]
}, },
"auth.css": { "auth.css": {
"originalSize": 23887, "originalSize": 20895,
"purgedSize": 23816, "purgedSize": 20895,
"rejectedCount": 4, "rejectedCount": 0,
"rejected": [ "rejected": []
"swp-btn.social",
"swp-btn.social:hover",
"swp-btn.social img",
"swp-plan-badge.free"
]
}, },
"attentions.css": { "attentions.css": {
"originalSize": 2524, "originalSize": 2524,
@ -197,8 +230,8 @@
"rejected": [] "rejected": []
}, },
"account.css": { "account.css": {
"originalSize": 8829, "originalSize": 7277,
"purgedSize": 8777, "purgedSize": 7225,
"rejectedCount": 2, "rejectedCount": 2,
"rejected": [ "rejected": [
"swp-invoice-status.pending", "swp-invoice-status.pending",

View file

@ -0,0 +1 @@
/c/Users/Janus Knudsen/source/swp-repos/PlanTempus/PlanTempus.Application

View file

@ -13,10 +13,32 @@ Reference for alle genbrugelige komponenter. **LAV ALDRIG EN NY KOMPONENT HVIS D
| `swp-page-title` | Titel-wrapper med h1 og p | h1 + subtitle | | `swp-page-title` | Titel-wrapper med h1 og p | h1 + subtitle |
| `swp-page-actions` | Action buttons i header | Flex gap | | `swp-page-actions` | Action buttons i header | Flex gap |
| `swp-card` | Standard card wrapper | Border, padding, rounded | | `swp-card` | Standard card wrapper | Border, padding, rounded |
| `swp-card-header` | Card header | Flex, title + action | | `swp-section-label` | Card section label | Uppercase, 11px, border-bottom |
| `swp-card-title` | Card titel med ikon | `<swp-card-title><i>...</i> Text</swp-card-title>` | | `swp-section-header` | Wrapper for label + action | Flex, space-between |
| `swp-section-action` | Action link i section header | Teal, clickable |
| `swp-card-content` | Card indhold | Block | | `swp-card-content` | Card indhold | Block |
### Card Header Eksempler
**Simpel label (uden action):**
```html
<swp-card>
<swp-section-label>Kontakter</swp-section-label>
<swp-card-content>...</swp-card-content>
</swp-card>
```
**Label med action:**
```html
<swp-card>
<swp-section-header>
<swp-section-label>Dagens bookinger</swp-section-label>
<swp-section-action>Se alle</swp-section-action>
</swp-section-header>
<swp-card-content>...</swp-card-content>
</swp-card>
```
--- ---
## Stats Components (stats.css) ## Stats Components (stats.css)

View file

@ -3,6 +3,8 @@
* *
* For logged-in users to manage their subscription plan, * For logged-in users to manage their subscription plan,
* payment method, and view invoice history. * payment method, and view invoice history.
*
* Reuses: swp-btn, swp-plan-card, swp-plan-badge, swp-plan-action (components.css)
*/ */
/* =========================================== /* ===========================================
@ -49,46 +51,7 @@ swp-plan-grid {
} }
} }
/* Plan card current state (extends auth.css) */ /* Plan card/badge/action styles in components.css */
swp-plan-card.current {
border-color: var(--color-teal);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-teal) 15%, transparent);
}
swp-plan-badge.current {
background: color-mix(in srgb, var(--color-teal) 15%, transparent);
color: var(--color-teal);
}
/* Disabled button for current plan */
swp-plan-card.current swp-btn.secondary {
background: var(--color-background-alt);
color: var(--color-text-secondary);
cursor: default;
pointer-events: none;
}
/* Enterprise plan styling */
swp-plan-card.enterprise {
background: linear-gradient(135deg, var(--color-surface) 0%, color-mix(in srgb, var(--color-purple) 5%, var(--color-surface)) 100%);
border-color: var(--color-purple);
}
swp-plan-badge.popular {
background: color-mix(in srgb, var(--color-amber) 15%, transparent);
color: var(--color-amber);
}
/* Plan action buttons */
swp-plan-action {
margin-top: auto;
padding-top: var(--card-padding);
}
swp-plan-action swp-btn {
width: 100%;
justify-content: center;
}
/* =========================================== /* ===========================================
BILLING GRID (2 columns) BILLING GRID (2 columns)
@ -343,37 +306,7 @@ swp-download-btn i {
font-size: 14px; font-size: 14px;
} }
/* =========================================== /* Button styles in components.css */
BUTTONS (account-specific)
=========================================== */
swp-btn.secondary {
background: var(--color-surface);
border: 1px solid var(--color-border);
color: var(--color-text);
}
swp-btn.secondary:hover {
background: var(--color-background-hover);
}
swp-btn.outline {
background: transparent;
border: 1px solid var(--color-teal);
color: var(--color-teal);
}
swp-btn.outline:hover {
background: color-mix(in srgb, var(--color-teal) 10%, transparent);
}
swp-btn.sm {
padding: var(--spacing-2) var(--spacing-3);
font-size: var(--font-size-xs);
}
swp-btn.sm i {
font-size: 14px;
}
/* =========================================== /* ===========================================
RESPONSIVE RESPONSIVE

View file

@ -3,6 +3,8 @@
* *
* Login, Signup, Password Reset etc. * Login, Signup, Password Reset etc.
* Split-screen SaaS layout pattern * Split-screen SaaS layout pattern
*
* Reuses: swp-btn, swp-plan-card, swp-plan-badge (components.css)
*/ */
/* =========================================== /* ===========================================
@ -124,46 +126,13 @@ swp-selected-plan-header {
} }
/* =========================================== /* ===========================================
PLAN CARD (Selected Plan) PLAN CONTENT (Auth-specific layout)
Plan card/badge base styles in components.css
=========================================== */ =========================================== */
swp-plan-card {
display: flex;
flex-direction: column;
background: var(--color-surface);
border: 2px solid var(--color-border);
border-radius: var(--radius-xl);
padding: var(--spacing-10);
transition: all var(--transition-normal);
}
swp-plan-card.selected {
border-color: var(--color-teal);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-teal) 15%, transparent);
}
swp-plan-badge { swp-plan-badge {
display: inline-flex;
align-items: center;
gap: var(--spacing-2);
padding: var(--spacing-2) var(--spacing-3);
font-size: var(--font-size-xs);
font-weight: var(--font-weight-semibold);
text-transform: uppercase;
letter-spacing: 0.5px;
border-radius: var(--radius-pill);
width: fit-content;
margin-bottom: var(--spacing-4); margin-bottom: var(--spacing-4);
} }
swp-plan-badge.selected {
background: color-mix(in srgb, var(--color-teal) 15%, transparent);
color: var(--color-teal);
}
swp-plan-badge i {
font-size: 14px;
}
swp-plan-name { swp-plan-name {
display: block; display: block;
font-size: var(--font-size-2xl); font-size: var(--font-size-2xl);
@ -589,52 +558,7 @@ swp-form-checkbox a:hover {
text-decoration: underline; text-decoration: underline;
} }
/* =========================================== /* Button base styles in components.css */
BUTTONS
=========================================== */
swp-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--spacing-2);
padding: var(--spacing-3) var(--spacing-6);
font-size: var(--font-size-base);
font-weight: var(--font-weight-medium);
font-family: var(--font-family);
border-radius: var(--border-radius);
border: 1px solid transparent;
cursor: pointer;
transition: all var(--transition-fast);
text-decoration: none;
}
swp-btn.primary {
background: var(--color-teal);
color: white;
border-color: var(--color-teal);
}
swp-btn.primary:hover {
background: #00796b;
border-color: #00796b;
}
swp-btn.primary:active {
transform: translateY(1px);
}
swp-btn.full-width {
width: 100%;
}
swp-btn.lg {
padding: var(--spacing-4) var(--spacing-8);
font-size: var(--font-size-lg);
}
swp-btn i {
font-size: 18px;
}
/* =========================================== /* ===========================================
AUTH FOOTER AUTH FOOTER
@ -692,21 +616,7 @@ swp-social-buttons {
gap: var(--spacing-3); gap: var(--spacing-3);
} }
swp-btn.social { /* Social button styles in components.css */
background: var(--color-background);
color: var(--color-text);
border-color: var(--color-border);
}
swp-btn.social:hover {
background: var(--color-background-hover);
border-color: var(--color-text-muted);
}
swp-btn.social img {
width: 20px;
height: 20px;
}
/* =========================================== /* ===========================================
FORM MESSAGES FORM MESSAGES
@ -819,72 +729,12 @@ swp-pricing-grid swp-plan-card.popular {
transform: scale(1.02); transform: scale(1.02);
} }
swp-pricing-grid swp-plan-card.enterprise { /* Plan badge/button styles in components.css */
background: linear-gradient(135deg, var(--color-surface) 0%, color-mix(in srgb, var(--color-purple) 5%, var(--color-surface)) 100%);
border-color: var(--color-purple);
}
swp-plan-badge.popular {
background: color-mix(in srgb, var(--color-teal) 15%, transparent);
color: var(--color-teal);
}
swp-plan-badge.enterprise {
background: color-mix(in srgb, var(--color-purple) 15%, transparent);
color: var(--color-purple);
}
swp-plan-badge.free {
background: color-mix(in srgb, var(--color-green) 15%, transparent);
color: var(--color-green);
}
swp-pricing-grid swp-plan-action { swp-pricing-grid swp-plan-action {
margin-top: var(--spacing-6); margin-top: var(--spacing-6);
} }
/* Button styles for pricing */
.swp-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--spacing-2);
padding: var(--spacing-3) var(--spacing-6);
font-size: var(--font-size-base);
font-weight: var(--font-weight-medium);
font-family: var(--font-family);
border-radius: var(--border-radius);
border: 1px solid transparent;
cursor: pointer;
transition: all var(--transition-fast);
text-decoration: none;
}
.swp-btn.primary {
background: var(--color-teal);
color: white;
border-color: var(--color-teal);
}
.swp-btn.primary:hover {
background: #00796b;
border-color: #00796b;
}
.swp-btn.outline {
background: transparent;
color: var(--color-purple);
border-color: var(--color-purple);
}
.swp-btn.outline:hover {
background: color-mix(in srgb, var(--color-purple) 10%, transparent);
}
.swp-btn.full-width {
width: 100%;
}
swp-pricing-footer { swp-pricing-footer {
margin-top: var(--spacing-12); margin-top: var(--spacing-12);
text-align: center; text-align: center;

View file

@ -96,6 +96,7 @@ swp-booking-employee {
gap: var(--spacing-3); gap: var(--spacing-3);
} }
/* TODO: Consider using swp-avatar.size-xs from components.css instead */
swp-booking-employee swp-avatar-small { swp-booking-employee swp-avatar-small {
width: 24px; width: 24px;
height: 24px; height: 24px;

View file

@ -3,6 +3,7 @@
* *
* Filter bar, stats, table, forms, and difference box * Filter bar, stats, table, forms, and difference box
* Reuses: swp-sticky-header, swp-header-content (page.css) * Reuses: swp-sticky-header, swp-header-content (page.css)
* Reuses: swp-btn, swp-status-badge, swp-card-footer (components.css)
*/ */
/* =========================================== /* ===========================================
@ -321,67 +322,14 @@ swp-cash-table-footer {
color: var(--color-text-secondary); color: var(--color-text-secondary);
} }
/* =========================================== /* Status badge styles in components.css */
STATUS BADGE
=========================================== */
/* Center status column */ /* Center status column */
swp-cash-th:nth-child(9), swp-cash-th:nth-child(9),
swp-cash-td:nth-child(9) { swp-cash-td:nth-child(9) {
text-align: center; text-align: center;
} }
swp-status-badge {
display: inline-flex;
align-items: center;
gap: var(--spacing-3);
padding: var(--spacing-2) var(--spacing-5);
font-size: var(--font-size-xs);
font-weight: var(--font-weight-medium);
border-radius: var(--radius-pill);
}
swp-status-badge::before {
content: '';
width: 6px;
height: 6px;
border-radius: var(--radius-full);
background: currentColor;
}
/* Status variants */
swp-status-badge.approved,
swp-status-badge.active {
background: color-mix(in srgb, var(--color-green) 15%, transparent);
color: var(--color-green);
}
swp-status-badge.draft,
swp-status-badge.invited {
background: color-mix(in srgb, var(--color-amber) 15%, transparent);
color: #b45309;
}
/* Role variants */
swp-status-badge.owner {
background: color-mix(in srgb, var(--color-teal) 15%, transparent);
color: var(--color-teal);
}
swp-status-badge.admin {
background: color-mix(in srgb, var(--color-purple) 15%, transparent);
color: var(--color-purple);
}
swp-status-badge.leader {
background: color-mix(in srgb, var(--color-blue) 15%, transparent);
color: var(--color-blue);
}
swp-status-badge.employee {
background: var(--color-background-alt);
color: var(--color-text-secondary);
}
/* =========================================== /* ===========================================
TWO-COLUMN GRID (Detail View) TWO-COLUMN GRID (Detail View)
=========================================== */ =========================================== */
@ -774,78 +722,14 @@ swp-checkbox-field label {
line-height: var(--line-height-normal); line-height: var(--line-height-normal);
} }
/* =========================================== /* Card footer base styles in components.css */
CARD FOOTER (Actions)
=========================================== */
swp-card-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--spacing-8) var(--spacing-10);
background: var(--color-background-alt);
border-top: 1px solid var(--color-border);
margin: var(--spacing-10) calc(-1 * var(--spacing-5)) calc(-1 * var(--spacing-5));
border-radius: 0 0 var(--border-radius-lg) var(--border-radius-lg);
}
swp-actions-right { swp-actions-right {
display: flex; display: flex;
gap: var(--spacing-5); gap: var(--spacing-5);
} }
/* =========================================== /* Button styles in components.css */
BUTTONS
=========================================== */
swp-btn {
display: inline-flex;
align-items: center;
gap: var(--spacing-3);
padding: var(--spacing-5) var(--spacing-8);
font-size: var(--font-size-md);
font-weight: var(--font-weight-medium);
font-family: var(--font-family);
border-radius: var(--radius-md);
cursor: pointer;
transition: all var(--transition-fast);
border: none;
}
swp-btn i {
font-size: var(--font-size-lg);
}
swp-btn.primary {
background: var(--color-teal);
color: white;
}
swp-btn.primary:hover {
opacity: 0.9;
}
swp-btn.primary:disabled {
background: var(--color-border);
cursor: not-allowed;
}
swp-btn.secondary {
background: var(--color-surface);
border: 1px solid var(--color-border);
color: var(--color-text);
}
swp-btn.secondary:hover {
background: var(--color-background-hover);
}
swp-btn.ghost {
background: transparent;
color: var(--color-text-secondary);
}
swp-btn.ghost:hover {
color: var(--color-text);
}
/* =========================================== /* ===========================================
SYSTEM NOTE SYSTEM NOTE

View file

@ -0,0 +1,434 @@
/**
* UI Components - Shared reusable components
*
* This file contains all shared UI components used across the application.
* Feature files should NOT define these components - only use them.
*
* Components:
* - swp-btn (buttons)
* - swp-status-badge (status/role badges)
* - swp-plan-card (subscription plan cards)
* - swp-avatar (user avatars with size variants)
* - swp-icon-btn (icon-only buttons)
* - swp-card (content cards with section-label/footer)
* - swp-section-label (card section headers)
* - swp-section-header (section header with action link)
* - swp-section-action (action link in section header)
*/
/* ===========================================
BUTTONS (swp-btn)
=========================================== */
swp-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--spacing-2);
padding: var(--spacing-3) var(--spacing-6);
font-size: var(--font-size-base);
font-weight: var(--font-weight-medium);
font-family: var(--font-family);
border-radius: var(--radius-md);
border: 1px solid transparent;
cursor: pointer;
transition: all var(--transition-fast);
text-decoration: none;
}
swp-btn i {
font-size: 18px;
}
/* Primary button */
swp-btn.primary {
background: var(--color-teal);
color: white;
border-color: var(--color-teal);
}
swp-btn.primary:hover {
background: #00796b;
border-color: #00796b;
}
swp-btn.primary:active {
transform: translateY(1px);
}
swp-btn.primary:disabled {
background: var(--color-border);
border-color: var(--color-border);
cursor: not-allowed;
}
/* Secondary button */
swp-btn.secondary {
background: var(--color-surface);
border: 1px solid var(--color-border);
color: var(--color-text);
}
swp-btn.secondary:hover {
background: var(--color-background-hover);
}
/* Ghost button */
swp-btn.ghost {
background: transparent;
color: var(--color-text-secondary);
}
swp-btn.ghost:hover {
color: var(--color-text);
}
/* Outline button */
swp-btn.outline {
background: transparent;
border: 1px solid var(--color-teal);
color: var(--color-teal);
}
swp-btn.outline:hover {
background: color-mix(in srgb, var(--color-teal) 10%, transparent);
}
swp-btn.outline.purple {
border-color: var(--color-purple);
color: var(--color-purple);
}
swp-btn.outline.purple:hover {
background: color-mix(in srgb, var(--color-purple) 10%, transparent);
}
/* Social button */
swp-btn.social {
background: var(--color-background);
color: var(--color-text);
border-color: var(--color-border);
}
swp-btn.social:hover {
background: var(--color-background-hover);
border-color: var(--color-text-muted);
}
swp-btn.social img {
width: 20px;
height: 20px;
}
/* Size variants */
swp-btn.sm {
padding: var(--spacing-2) var(--spacing-3);
font-size: var(--font-size-xs);
}
swp-btn.sm i {
font-size: 14px;
}
swp-btn.lg {
padding: var(--spacing-4) var(--spacing-8);
font-size: var(--font-size-lg);
}
/* Full width */
swp-btn.full-width {
width: 100%;
}
/* ===========================================
STATUS BADGES (swp-status-badge)
=========================================== */
swp-status-badge {
display: inline-flex;
align-items: center;
gap: var(--spacing-3);
padding: var(--spacing-2) var(--spacing-5);
font-size: var(--font-size-xs);
font-weight: var(--font-weight-medium);
border-radius: var(--radius-pill);
}
swp-status-badge::before {
content: '';
width: 6px;
height: 6px;
border-radius: var(--radius-full);
background: currentColor;
}
/* Status variants */
swp-status-badge.approved,
swp-status-badge.active {
background: color-mix(in srgb, var(--color-green) 15%, transparent);
color: var(--color-green);
}
swp-status-badge.draft,
swp-status-badge.invited {
background: color-mix(in srgb, var(--color-amber) 15%, transparent);
color: #b45309;
}
/* Role variants */
swp-status-badge.owner {
background: color-mix(in srgb, var(--color-teal) 15%, transparent);
color: var(--color-teal);
}
swp-status-badge.admin {
background: color-mix(in srgb, var(--color-purple) 15%, transparent);
color: var(--color-purple);
}
swp-status-badge.leader {
background: color-mix(in srgb, var(--color-blue) 15%, transparent);
color: var(--color-blue);
}
swp-status-badge.employee {
background: var(--color-background-alt);
color: var(--color-text-secondary);
}
/* ===========================================
PLAN CARDS (swp-plan-card)
=========================================== */
swp-plan-card {
display: flex;
flex-direction: column;
background: var(--color-surface);
border: 2px solid var(--color-border);
border-radius: var(--radius-xl);
padding: var(--spacing-10);
transition: all var(--transition-normal);
}
swp-plan-card.selected,
swp-plan-card.current {
border-color: var(--color-teal);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-teal) 15%, transparent);
}
swp-plan-card.popular {
border-color: var(--color-amber);
}
swp-plan-card.enterprise {
background: linear-gradient(135deg, var(--color-surface) 0%, color-mix(in srgb, var(--color-purple) 5%, var(--color-surface)) 100%);
border-color: var(--color-purple);
}
/* Disabled button for current plan */
swp-plan-card.current swp-btn.secondary {
background: var(--color-background-alt);
color: var(--color-text-secondary);
cursor: default;
pointer-events: none;
}
/* Plan badge */
swp-plan-badge {
display: inline-flex;
align-items: center;
gap: var(--spacing-2);
padding: var(--spacing-2) var(--spacing-3);
font-size: var(--font-size-xs);
font-weight: var(--font-weight-semibold);
text-transform: uppercase;
letter-spacing: 0.5px;
border-radius: var(--radius-pill);
width: fit-content;
}
swp-plan-badge.current,
swp-plan-badge.selected {
background: color-mix(in srgb, var(--color-teal) 15%, transparent);
color: var(--color-teal);
}
swp-plan-badge.popular {
background: color-mix(in srgb, var(--color-amber) 15%, transparent);
color: var(--color-amber);
}
swp-plan-badge.enterprise {
background: color-mix(in srgb, var(--color-purple) 15%, transparent);
color: var(--color-purple);
}
swp-plan-badge.free {
background: color-mix(in srgb, var(--color-green) 15%, transparent);
color: var(--color-green);
}
swp-plan-badge i {
font-size: 14px;
}
/* Plan action */
swp-plan-action {
margin-top: auto;
padding-top: var(--card-padding);
}
swp-plan-action swp-btn {
width: 100%;
}
/* ===========================================
AVATARS (swp-avatar)
=========================================== */
swp-avatar {
border-radius: 50%;
background: var(--color-teal);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: var(--font-weight-semibold);
flex-shrink: 0;
}
/* Default size (md = 40px) */
swp-avatar {
width: 40px;
height: 40px;
font-size: var(--font-size-sm);
}
/* Size variants */
swp-avatar.size-xs {
width: 24px;
height: 24px;
font-size: 10px;
}
swp-avatar.size-sm {
width: 36px;
height: 36px;
font-size: var(--font-size-xs);
}
swp-avatar.size-lg {
width: 80px;
height: 80px;
font-size: var(--font-size-2xl);
}
/* Color variants */
swp-avatar.purple {
background: var(--color-purple);
}
swp-avatar.blue {
background: var(--color-blue);
}
swp-avatar.amber {
background: var(--color-amber);
}
swp-avatar.teal {
background: var(--color-teal);
}
/* ===========================================
ICON BUTTONS (swp-icon-btn)
=========================================== */
swp-icon-btn {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--radius-md);
background: transparent;
border: none;
color: var(--color-text-secondary);
cursor: pointer;
transition: all var(--transition-fast);
}
swp-icon-btn:hover {
background: var(--color-background-alt);
color: var(--color-text);
}
swp-icon-btn.danger:hover {
background: color-mix(in srgb, var(--color-red) 10%, transparent);
color: var(--color-red);
}
swp-icon-btn i {
font-size: 18px;
}
/* ===========================================
CARDS (swp-card)
=========================================== */
swp-card {
display: block;
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-lg);
padding: var(--card-padding);
}
/* Section label - simple card header */
swp-section-label {
display: block;
font-size: var(--font-size-xs);
font-weight: var(--font-weight-semibold);
color: var(--color-text);
text-transform: uppercase;
letter-spacing: 0.5px;
padding-bottom: var(--spacing-4);
border-bottom: 1px solid var(--color-border);
margin-bottom: var(--spacing-6);
}
/* Section header - wrapper when action link is needed */
swp-section-header {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: var(--spacing-4);
border-bottom: 1px solid var(--color-border);
margin-bottom: var(--spacing-6);
}
swp-section-header swp-section-label {
padding-bottom: 0;
border-bottom: none;
margin-bottom: 0;
}
swp-section-action {
font-size: var(--font-size-sm);
color: var(--color-teal);
cursor: pointer;
transition: opacity var(--transition-fast);
}
swp-section-action:hover {
opacity: 0.8;
}
swp-card-content {
display: block;
}
swp-card-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--spacing-6) var(--card-padding);
background: var(--color-background-alt);
border-top: 1px solid var(--color-border);
margin: var(--spacing-6) calc(-1 * var(--card-padding)) calc(-1 * var(--card-padding));
border-radius: 0 0 var(--border-radius-lg) var(--border-radius-lg);
}

View file

@ -3,7 +3,8 @@
* *
* Employees-specific styling only. * Employees-specific styling only.
* Reuses: swp-stat-card (stats.css), swp-stats-row (stats.css), swp-tab-bar (tabs.css), * Reuses: swp-stat-card (stats.css), swp-stats-row (stats.css), swp-tab-bar (tabs.css),
* swp-btn (cash.css), swp-status-badge (cash.css), swp-row-toggle (cash.css), * swp-btn, swp-status-badge, swp-icon-btn, swp-card, swp-section-label (components.css),
* swp-row-toggle (cash.css),
* swp-sticky-header, swp-header-content (page.css), * swp-sticky-header, swp-header-content (page.css),
* swp-toggle-slider, swp-checkbox-list (controls.css) * swp-toggle-slider, swp-checkbox-list (controls.css)
* *
@ -57,8 +58,8 @@ swp-users-progress-bar {
swp-employee-table-card { swp-employee-table-card {
display: block; display: block;
background: var(--color-surface); background: var(--color-surface);
border-radius: var(--radius-lg); border: 1px solid var(--color-border);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); border-radius: var(--border-radius-lg);
overflow: hidden; overflow: hidden;
} }
@ -195,33 +196,7 @@ swp-table-actions {
justify-content: flex-end; justify-content: flex-end;
} }
swp-icon-btn { /* Icon button styles in components.css */
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--radius-md);
background: transparent;
border: none;
color: var(--color-text-secondary);
cursor: pointer;
transition: all var(--transition-fast);
}
swp-icon-btn:hover {
background: var(--color-background-alt);
color: var(--color-text);
}
swp-icon-btn.danger:hover {
background: color-mix(in srgb, var(--color-red) 10%, transparent);
color: var(--color-red);
}
swp-icon-btn i {
font-size: 18px;
}
/* =========================================== /* ===========================================
PERMISSIONS MATRIX PERMISSIONS MATRIX
@ -452,7 +427,7 @@ swp-edit-row {
} }
swp-edit-label { swp-edit-label {
font-size: var(--font-size-sm); font-size: var(--font-size-md);
color: var(--color-text-secondary); color: var(--color-text-secondary);
} }
@ -477,16 +452,7 @@ swp-edit-value:focus {
border-color: var(--color-teal); border-color: var(--color-teal);
} }
/* Section label in cards */ /* swp-section-label is now in components.css */
swp-section-label {
display: block;
font-size: var(--font-size-sm);
font-weight: var(--font-weight-semibold);
color: var(--color-text);
margin-bottom: var(--spacing-6);
padding-bottom: var(--spacing-4);
border-bottom: 1px solid var(--color-border);
}
/* =========================================== /* ===========================================
VIEW CONTAINERS (List/Detail swap) VIEW CONTAINERS (List/Detail swap)
@ -532,12 +498,7 @@ swp-detail-grid > div {
gap: var(--spacing-8); gap: var(--spacing-8);
} }
swp-detail-grid swp-card { /* swp-card base styles in components.css */
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
padding: var(--card-padding);
}
@media (max-width: 900px) { @media (max-width: 900px) {
swp-detail-grid { swp-detail-grid {

View file

@ -78,54 +78,9 @@ swp-page-actions {
} }
/* =========================================== /* ===========================================
CARDS CARD CONTENT GRIDS (Page-specific)
Base card styles in components.css
=========================================== */ =========================================== */
swp-card {
display: block;
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-lg);
padding: var(--card-padding);
}
swp-card-header {
display: flex;
align-items: center;
justify-content: space-between;
margin: calc(-1 * var(--card-padding)) calc(-1 * var(--card-padding)) var(--spacing-5) calc(-1 * var(--card-padding));
padding: var(--spacing-4) var(--card-padding);
border-bottom: 1px solid var(--color-border);
}
swp-card-title {
display: flex;
align-items: center;
gap: var(--spacing-2);
font-size: var(--font-size-base);
font-weight: var(--font-weight-semibold);
color: var(--color-text);
}
swp-card-title i {
font-size: var(--font-size-xl);
color: var(--color-text-secondary);
}
swp-card-action {
font-size: var(--font-size-md);
color: var(--color-teal);
cursor: pointer;
transition: opacity var(--transition-fast);
}
swp-card-action:hover {
opacity: 0.8;
}
swp-card-content {
display: block;
}
/* Card content with grid lists - auto-detect via :has() */ /* Card content with grid lists - auto-detect via :has() */
swp-card-content:has(> swp-booking-list), swp-card-content:has(> swp-booking-list),
swp-card-content:has(> swp-notification-list), swp-card-content:has(> swp-notification-list),

View file

@ -1,38 +0,0 @@
/**
* Quick Stats CSS
*
* Styling for quick stats components in sidebar
*/
/* ===========================================
QUICK STATS CONTAINER
=========================================== */
swp-quick-stats {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: var(--card-gap);
}
/* ===========================================
QUICK STAT ITEM
=========================================== */
swp-quick-stat {
display: flex;
flex-direction: column;
gap: var(--spacing-2);
padding: var(--card-padding);
background: var(--color-background-alt);
border-radius: var(--radius-md);
}
swp-quick-stat swp-stat-value {
font-size: var(--font-size-xl);
font-weight: var(--font-weight-semibold);
font-family: var(--font-mono);
color: var(--color-text);
}
swp-quick-stat swp-stat-label {
font-size: var(--font-size-xs);
color: var(--color-text-secondary);
}

View file

@ -208,6 +208,36 @@ swp-stat-item swp-stat-label {
margin-top: 0; margin-top: 0;
} }
/* ===========================================
QUICK STATS (Sidebar Variant)
=========================================== */
swp-quick-stats {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: var(--card-gap);
}
swp-quick-stat {
display: flex;
flex-direction: column;
gap: var(--spacing-2);
padding: var(--card-padding);
background: var(--color-background-alt);
border-radius: var(--radius-md);
}
swp-quick-stat swp-stat-value {
font-size: var(--font-size-xl);
font-weight: var(--font-weight-semibold);
font-family: var(--font-mono);
color: var(--color-text);
}
swp-quick-stat swp-stat-label {
font-size: var(--font-size-xs);
color: var(--color-text-secondary);
}
/* =========================================== /* ===========================================
RESPONSIVE RESPONSIVE
=========================================== */ =========================================== */

View file

@ -0,0 +1,118 @@
/**
* Utilities - Common utility classes
*
* Reusable utility classes for common patterns.
* Use sparingly - prefer semantic component styles.
*/
/* ===========================================
FLEXBOX UTILITIES
=========================================== */
.flex-center {
display: flex;
align-items: center;
justify-content: center;
}
.flex-between {
display: flex;
justify-content: space-between;
align-items: center;
}
.flex-start {
display: flex;
align-items: center;
justify-content: flex-start;
}
.flex-end {
display: flex;
align-items: center;
justify-content: flex-end;
}
.flex-column {
display: flex;
flex-direction: column;
}
.flex-1 {
flex: 1;
}
.flex-shrink-0 {
flex-shrink: 0;
}
/* ===========================================
TEXT UTILITIES
=========================================== */
.text-truncate {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.text-center {
text-align: center;
}
.text-right {
text-align: right;
}
.text-secondary {
font-size: var(--font-size-sm);
color: var(--color-text-secondary);
}
.text-muted {
color: var(--color-text-muted);
}
.text-mono {
font-family: var(--font-mono);
}
/* ===========================================
SPACING UTILITIES
=========================================== */
.gap-2 { gap: var(--spacing-2); }
.gap-3 { gap: var(--spacing-3); }
.gap-4 { gap: var(--spacing-4); }
.gap-6 { gap: var(--spacing-6); }
.gap-8 { gap: var(--spacing-8); }
.mt-auto { margin-top: auto; }
.mb-0 { margin-bottom: 0; }
/* ===========================================
VISIBILITY UTILITIES
=========================================== */
.hidden {
display: none !important;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* ===========================================
WIDTH UTILITIES
=========================================== */
.w-full {
width: 100%;
}
.min-w-0 {
min-width: 0;
}

View file

@ -2,6 +2,8 @@
* Waitlist CSS * Waitlist CSS
* *
* Styling for waitlist mini card and drawer items * Styling for waitlist mini card and drawer items
*
* Reuses: swp-btn, swp-avatar (components.css)
*/ */
/* =========================================== /* ===========================================
@ -84,19 +86,7 @@ swp-waitlist-customer {
gap: var(--spacing-6); gap: var(--spacing-6);
} }
swp-waitlist-customer swp-avatar { /* Avatar base styles in components.css */
width: 40px;
height: 40px;
border-radius: var(--radius-full);
background: var(--color-teal);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: var(--font-size-base);
font-weight: var(--font-weight-semibold);
flex-shrink: 0;
}
swp-waitlist-customer-info { swp-waitlist-customer-info {
flex: 1; flex: 1;
@ -191,39 +181,9 @@ swp-waitlist-actions {
border-top: 1px solid var(--color-border); border-top: 1px solid var(--color-border);
} }
/* Button base styles in components.css */
swp-waitlist-actions swp-btn { swp-waitlist-actions swp-btn {
flex: 1; flex: 1;
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--spacing-2);
padding: var(--spacing-4) var(--spacing-6);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
font-family: var(--font-family);
border-radius: var(--radius-md);
cursor: pointer;
transition: all var(--transition-fast);
border: none;
}
swp-waitlist-actions swp-btn.secondary {
background: var(--color-surface);
border: 1px solid var(--color-border);
color: var(--color-text);
}
swp-waitlist-actions swp-btn.secondary:hover {
background: var(--color-background-hover);
}
swp-waitlist-actions swp-btn.primary {
background: var(--color-teal);
color: white;
}
swp-waitlist-actions swp-btn.primary:hover {
opacity: 0.9;
} }
/* =========================================== /* ===========================================

1
tmpclaude-6265-cwd Normal file
View file

@ -0,0 +1 @@
/c/Users/Janus Knudsen/source/swp-repos/PlanTempus

1
tmpclaude-6f72-cwd Normal file
View file

@ -0,0 +1 @@
/c/Users/Janus Knudsen/source/swp-repos/PlanTempus

1
tmpclaude-878e-cwd Normal file
View file

@ -0,0 +1 @@
/c/Users/Janus Knudsen/source/swp-repos/PlanTempus

1
tmpclaude-8eac-cwd Normal file
View file

@ -0,0 +1 @@
/c/Users/Janus Knudsen/source/swp-repos/PlanTempus