+94
-71
Diff
round #0
+1
-1
internal/web/components/layout.templ
+1
-1
internal/web/components/layout.templ
···
115
115
<link rel="icon" href="/static/favicon.svg" type="image/svg+xml"/>
116
116
<link rel="icon" href="/static/favicon-32.svg" type="image/svg+xml" sizes="32x32"/>
117
117
<link rel="apple-touch-icon" href="/static/icon-192.svg"/>
118
-
<link rel="stylesheet" href="/static/css/output.css?v=0.12.0"/>
118
+
<link rel="stylesheet" href="/static/css/output.css?v=0.13.0"/>
119
119
<style>
120
120
[x-cloak] { display: none !important; }
121
121
</style>
+15
-41
internal/web/pages/bean_view.templ
+15
-41
internal/web/pages/bean_view.templ
···
27
27
templ BeanViewCard(props BeanViewProps) {
28
28
@components.RecordViewHeader(components.RecordViewHeaderProps{
29
29
RecordType: "bean",
30
-
Title: beanViewTitle(props.Bean),
30
+
Title: "",
31
31
Timestamp: props.Bean.CreatedAt.Format("January 2, 2006 at 3:04 PM"),
32
32
TimestampISO: bff.FormatISO(props.Bean.CreatedAt),
33
33
AuthorDID: props.AuthorDID,
···
36
36
AuthorAvatar: props.AuthorAvatar,
37
37
})
38
38
<div class="record-label p-4">
39
+
<h1 class="label-origin-hero">{ beanViewTitle(props.Bean) }</h1>
39
40
if props.Bean.Roaster != nil && props.Bean.Roaster.Name != "" {
40
41
<div class="label-byline">
41
-
<span class="inline-flex items-center gap-1">
42
-
@components.IconStore()
43
-
<a href={ templ.SafeURL(fmt.Sprintf("/roasters/%s?owner=%s", props.Bean.Roaster.RKey, getOwnerFromShareURL(props.ShareURL))) }>
44
-
{ props.Bean.Roaster.Name }
45
-
</a>
46
-
if props.Bean.Roaster.Location != "" {
47
-
<span class="text-faint">·</span>
48
-
<span class="inline-flex items-center gap-1">
49
-
@components.IconMapPin()
50
-
{ props.Bean.Roaster.Location }
51
-
</span>
52
-
}
53
-
</span>
42
+
<a href={ templ.SafeURL(fmt.Sprintf("/roasters/%s?owner=%s", props.Bean.Roaster.RKey, getOwnerFromShareURL(props.ShareURL))) }>
43
+
{ props.Bean.Roaster.Name }
44
+
</a>
45
+
if props.Bean.Roaster.Location != "" {
46
+
<span class="text-faint"> · { props.Bean.Roaster.Location }</span>
47
+
}
54
48
</div>
55
49
}
56
50
if props.Bean.Rating != nil {
57
-
<div class="brew-rating-hero mt-4">
51
+
<div class="brew-rating-hero mt-5">
58
52
<span class="brew-rating-value">{ fmt.Sprintf("%d", *props.Bean.Rating) }</span>
59
-
<span class="brew-rating-max">/10</span>
53
+
<span class="brew-rating-max">/ 10</span>
60
54
</div>
61
55
}
62
56
<div class="label-tags">
63
57
if props.Bean.Origin != "" {
64
-
<span class="label-tag">
65
-
<span class="inline-flex items-center gap-1">
66
-
@components.IconMapPin()
67
-
{ props.Bean.Origin }
68
-
</span>
69
-
</span>
58
+
<span class="label-tag">{ props.Bean.Origin }</span>
70
59
}
71
60
if props.Bean.Variety != "" {
72
-
<span class="label-tag">
73
-
<span class="inline-flex items-center gap-1">
74
-
@components.IconLeaf()
75
-
{ props.Bean.Variety }
76
-
</span>
77
-
</span>
61
+
<span class="label-tag">{ props.Bean.Variety }</span>
78
62
}
79
63
if props.Bean.RoastLevel != "" {
80
-
<span class="label-tag">
81
-
<span class="inline-flex items-center gap-1">
82
-
@components.IconFlame()
83
-
{ props.Bean.RoastLevel }
84
-
</span>
85
-
</span>
64
+
<span class="label-tag">{ props.Bean.RoastLevel }</span>
86
65
}
87
66
if props.Bean.Process != "" {
88
-
<span class="label-tag">
89
-
<span class="inline-flex items-center gap-1">
90
-
@components.IconSprout()
91
-
{ props.Bean.Process }
92
-
</span>
93
-
</span>
67
+
<span class="label-tag">{ props.Bean.Process }</span>
94
68
}
95
69
if props.Bean.Closed {
96
-
<span class="label-tag" style="border-color: var(--text-muted)">Closed</span>
70
+
<span class="label-tag label-tag-closed">Closed</span>
97
71
}
98
72
</div>
99
73
if props.Bean.Description != "" {
+14
-11
internal/web/pages/brew_view.templ
+14
-11
internal/web/pages/brew_view.templ
···
48
48
templ BrewViewCard(props BrewViewProps) {
49
49
@components.RecordViewHeader(components.RecordViewHeaderProps{
50
50
RecordType: "brew",
51
-
Title: "Brew Details",
51
+
Title: getBrewShareTitle(props.Brew),
52
52
Timestamp: props.Brew.CreatedAt.Format("January 2, 2006 at 3:04 PM"),
53
53
TimestampISO: bff.FormatISO(props.Brew.CreatedAt),
54
54
AuthorDID: props.AuthorDID,
···
60
60
@BrewSummary(props.Brew)
61
61
@BrewBeanSection(props.Brew, getOwnerFromShareURL(props.ShareURL))
62
62
<div class="my-6">
63
+
<div class="ledger-section">Inputs</div>
63
64
@components.JournalField(components.DetailStackedProps{Icon: components.IconScale(), Label: "Coffee", Value: getCoffeeAmountDisplay(props.Brew)})
64
-
@components.JournalField(components.DetailStackedProps{Icon: components.IconCoffee(), Label: "Brew Method", Value: getBrewerName(props.Brew), LinkHref: getBrewerViewURL(props.Brew, getOwnerFromShareURL(props.ShareURL))})
65
+
@components.JournalField(components.DetailStackedProps{Icon: components.IconDroplet(), Label: "Water", Value: getWaterAmountDisplay(props.Brew)})
65
66
@components.JournalField(components.DetailStackedProps{Icon: components.IconGear(), Label: "Grinder", Value: getGrinderName(props.Brew), LinkHref: getGrinderViewURL(props.Brew, getOwnerFromShareURL(props.ShareURL))})
66
67
@components.JournalField(components.DetailStackedProps{Icon: components.IconDisc(), Label: "Grind Size", Value: getGrindSizeDisplay(props.Brew)})
67
-
@components.JournalField(components.DetailStackedProps{Icon: components.IconDroplet(), Label: "Water", Value: getWaterAmountDisplay(props.Brew)})
68
68
@components.JournalField(components.DetailStackedProps{Icon: components.IconThermometer(), Label: "Temperature", Value: getTemperatureDisplay(props.Brew)})
69
+
if props.Brew.PouroverParams != nil && props.Brew.PouroverParams.Filter != "" {
70
+
@components.JournalField(components.DetailStackedProps{Icon: components.IconSliders(), Label: "Filter", Value: props.Brew.PouroverParams.Filter})
71
+
}
72
+
<div class="ledger-section">Process</div>
73
+
@components.JournalField(components.DetailStackedProps{Icon: components.IconCoffee(), Label: "Brew Method", Value: getBrewerName(props.Brew), LinkHref: getBrewerViewURL(props.Brew, getOwnerFromShareURL(props.ShareURL))})
69
74
@components.JournalField(components.DetailStackedProps{Icon: components.IconClock(), Label: "Brew Time", Value: getBrewTimeDisplay(props.Brew)})
70
75
if props.Brew.EspressoParams != nil {
71
-
if props.Brew.EspressoParams.YieldWeight > 0 {
72
-
@components.JournalField(components.DetailStackedProps{Icon: components.IconScale(), Label: "Yield", Value: fmt.Sprintf("%.1fg", props.Brew.EspressoParams.YieldWeight)})
76
+
if props.Brew.EspressoParams.PreInfusionSeconds > 0 {
77
+
@components.JournalField(components.DetailStackedProps{Icon: components.IconClock(), Label: "Pre-infusion", Value: fmt.Sprintf("%ds", props.Brew.EspressoParams.PreInfusionSeconds)})
73
78
}
74
79
if props.Brew.EspressoParams.Pressure > 0 {
75
80
@components.JournalField(components.DetailStackedProps{Icon: components.IconBarChart(), Label: "Pressure", Value: fmt.Sprintf("%.1f bar", props.Brew.EspressoParams.Pressure)})
76
81
}
77
-
if props.Brew.EspressoParams.PreInfusionSeconds > 0 {
78
-
@components.JournalField(components.DetailStackedProps{Icon: components.IconClock(), Label: "Pre-infusion", Value: fmt.Sprintf("%ds", props.Brew.EspressoParams.PreInfusionSeconds)})
79
-
}
80
82
}
81
83
if props.Brew.PouroverParams != nil {
82
84
if props.Brew.PouroverParams.BloomWater > 0 || props.Brew.PouroverParams.BloomSeconds > 0 {
···
88
90
if props.Brew.PouroverParams.BypassWater > 0 {
89
91
@components.JournalField(components.DetailStackedProps{Icon: components.IconDroplet(), Label: "Bypass Water", Value: fmt.Sprintf("%dg", props.Brew.PouroverParams.BypassWater)})
90
92
}
91
-
if props.Brew.PouroverParams.Filter != "" {
92
-
@components.JournalField(components.DetailStackedProps{Icon: components.IconSliders(), Label: "Filter", Value: props.Brew.PouroverParams.Filter})
93
-
}
93
+
}
94
+
if props.Brew.EspressoParams != nil && props.Brew.EspressoParams.YieldWeight > 0 {
95
+
<div class="ledger-section">Output</div>
96
+
@components.JournalField(components.DetailStackedProps{Icon: components.IconScale(), Label: "Yield", Value: fmt.Sprintf("%.1fg", props.Brew.EspressoParams.YieldWeight)})
94
97
}
95
98
</div>
96
99
<div class="space-y-6">
+12
-10
internal/web/pages/recipe_view.templ
+12
-10
internal/web/pages/recipe_view.templ
···
55
55
AuthorAvatar: props.AuthorAvatar,
56
56
})
57
57
if props.SourceRecipeURL != "" {
58
-
<p class="text-sm text-brown-500 -mt-4 mb-4">
59
-
Forked from 
60
-
<a href={ templ.SafeURL(props.SourceRecipeURL) } class="text-brown-700 underline hover:text-brown-900">
61
-
if props.SourceRecipeAuthor != "" {
62
-
{ props.SourceRecipeAuthor + "'s recipe" }
63
-
} else {
64
-
original recipe
65
-
}
66
-
</a>
67
-
</p>
58
+
<div class="-mt-3">
59
+
<span class="fork-chip">
60
+
↳ forked from 
61
+
<a href={ templ.SafeURL(props.SourceRecipeURL) }>
62
+
if props.SourceRecipeAuthor != "" {
63
+
{ props.SourceRecipeAuthor + "'s recipe" }
64
+
} else {
65
+
original recipe
66
+
}
67
+
</a>
68
+
</span>
69
+
</div>
68
70
}
69
71
<div class="record-journal p-4">
70
72
<div>
+49
-7
static/css/app.css
+49
-7
static/css/app.css
···
538
538
/* Record view — tinted header region */
539
539
.record-view-header {
540
540
@apply -mx-6 -mt-6 px-6 pt-5 pb-4 mb-6 rounded-t-xl;
541
+
border-bottom: 1px solid var(--surface-border);
541
542
}
542
543
543
544
.record-view-header-brew {
···
669
670
}
670
671
671
672
.brew-rating-value {
672
-
@apply text-4xl font-bold tracking-tight;
673
+
@apply font-bold tracking-tight leading-none;
674
+
font-size: clamp(3.25rem, 6vw + 1rem, 4.5rem);
673
675
color: var(--rating-text);
674
676
}
675
677
676
678
.brew-rating-max {
677
-
@apply text-lg font-medium;
679
+
@apply text-xs font-medium uppercase tracking-wider self-end pb-1;
678
680
color: var(--text-muted);
679
681
}
680
682
···
722
724
}
723
725
}
724
726
727
+
/* Section heading inside the journal ledger — separates Inputs/Process/Output */
728
+
.ledger-section {
729
+
@apply text-xs font-semibold uppercase mt-7 mb-1 pb-1;
730
+
letter-spacing: 0.18em;
731
+
color: var(--text-muted);
732
+
border-bottom: 1px solid var(--surface-border);
733
+
}
734
+
.ledger-section:first-child {
735
+
@apply mt-0;
736
+
}
737
+
738
+
/* Kraft tape chip — used for recipe fork provenance */
739
+
.fork-chip {
740
+
@apply inline-flex items-center gap-1 text-xs font-medium px-3 py-1 mb-5;
741
+
background-color: var(--kraft-bg);
742
+
background-image: var(--texture-kraft);
743
+
color: var(--text-secondary);
744
+
border: 1px solid var(--surface-border);
745
+
border-radius: 2px;
746
+
transform: rotate(-1.25deg);
747
+
box-shadow: var(--shadow-sm);
748
+
}
749
+
.fork-chip a {
750
+
color: var(--text-primary);
751
+
text-decoration: underline;
752
+
text-decoration-color: var(--text-faint);
753
+
text-underline-offset: 2px;
754
+
}
755
+
.fork-chip a:hover {
756
+
text-decoration-color: var(--text-muted);
757
+
}
758
+
725
759
/* Pour rows: shared by brew and recipe views */
726
760
.pour-row {
727
761
@apply flex gap-4 text-sm py-2;
···
778
812
color: var(--text-primary);
779
813
}
780
814
781
-
/* Inline tag strip: variety / process / roast level */
815
+
/* Inline tag strip: variety / process / roast level — stamp marks, not pills */
782
816
.label-tags {
783
-
@apply flex flex-wrap gap-2 mt-4;
817
+
@apply flex flex-wrap gap-x-3 gap-y-2 mt-5;
784
818
}
785
819
786
820
.label-tag {
787
-
@apply text-xs font-medium px-2.5 py-1 rounded-full;
788
-
background: var(--surface-bg);
821
+
@apply text-[0.7rem] font-semibold uppercase px-2.5 py-1;
822
+
letter-spacing: 0.14em;
823
+
background: transparent;
789
824
color: var(--text-secondary);
790
-
border: 1px solid var(--surface-border);
825
+
border: 1px solid var(--text-muted);
826
+
border-radius: 2px;
827
+
}
828
+
829
+
.label-tag-closed {
830
+
color: var(--text-muted);
831
+
border-color: var(--text-faint);
832
+
border-style: dashed;
791
833
}
792
834
793
835
/* Description on a label — flowing paragraph, no indent */
History
1 round
0 comments
pdewey.com
submitted
#0
1 commit
expand
collapse
feat: nicer bean page styling
merge conflicts detected
expand
collapse
expand
collapse
- internal/web/components/layout.templ:115
- internal/web/components/shared.templ:218
- internal/web/pages/bean_view.templ:53
- internal/web/pages/brew_view.templ:56
- internal/web/pages/brewer_view.templ:53
- internal/web/pages/grinder_view.templ:53
- internal/web/pages/recipe_view.templ:66
- internal/web/pages/roaster_view.templ:53
- static/css/app.css:561